Telemetry
A telemetry endpoint collects high-volume product events — clicks, page views, funnel steps — instead of leads.
It is the same POST /f/:id you already use. What changes is everything downstream: telemetry is metered on its own counter, never scored for spam, never fanned out to a destination, and swept on a short retention window.
Why it’s a separate endpoint kind
A click stream and a contact form have nothing in common except their shape. Sending both to one endpoint means the click stream eats the allowance that gates your lead delivery, and a busy Tuesday silently stops the emails you actually care about.
Telemetry endpoints exist so that can’t happen. The two counters are disjoint by construction: an event on a telemetry endpoint never counts toward submissions_per_month, and a lead submission never counts toward your telemetry allowance. A telemetry endpoint sitting at its cap has no effect on your form endpoints.
Create one
Pass kind: "telemetry" when you create the endpoint. This is an authenticated call — telemetry endpoints are always owned, never anonymous.
curl -X POST https://api.gopigeon.dev/api/forms \ -H "Authorization: Bearer $GOPIGEON_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"name":"Site events","kind":"telemetry"}'
The kind is fixed at creation and cannot be changed afterward. Deciding it up front is deliberate: inferring it from “this endpoint has no destinations” would silently reclassify — and silently re-meter — a real lead form the moment you removed its last destination.
Zero destinations, enforced
A telemetry endpoint can never fan out. Supplying recipient or destinations at creation returns HTTP 400, and so does attaching one later via POST /api/forms/:id/destinations. Events are stored and read back through the submissions API; nothing is emailed, posted, or webhooked.
Send events
Send fire-and-forget so telemetry never blocks the interaction it rides on. keepalive: true lets the request survive the navigation that triggered it — and also lets the browser retry it on its own, which is what Idempotency-Key is for.
// Fire-and-forget: never blocks the click it rides on. void fetch('https://api.gopigeon.dev/f/f_abc123def456xyz0', { method: 'POST', keepalive: true, headers: { 'Content-Type': 'application/json', // Same key on every retry of THIS event; a fresh one per new event. 'Idempotency-Key': crypto.randomUUID() }, body: JSON.stringify({ event: 'direct_booking_click', page: location.pathname }) }).catch(() => {});
Generate a fresh key per logical event and reuse it verbatim on any retry of that event. A repeat carrying a key already seen on this endpoint returns the original submission_id with idempotent_replay: true — no second row, no quota consumed. See Submissions for the full header contract.
No spam scoring
The _gotcha honeypot is not evaluated on telemetry endpoints, and every telemetry row is stored with is_spam: 0. A spam verdict on an identity-less direct_booking_click — no email, no message, no free text for a bot to fill — carries no signal, and it would leave you with a where not is_spam filter downstream that never excludes anything.
Honeypot fields are still stripped from stored data. They are just never scored.
Limits and retention
| Free | Pro | |
|---|---|---|
| Events per month | 5,000 | 100,000 |
| Retention | 7 days | 30 days |
The generous allowance and the short window are the same decision. Nobody re-reads a nine-month-old click, so telemetry is swept on a schedule rather than kept indefinitely — which is what makes the volume affordable to offer. Pull events into your own warehouse if you need them longer.
Over the monthly allowance, the endpoint returns HTTP 402:
{ "error": "monthly telemetry event quota exceeded", "kind": "telemetry", "plan": "free", "form_id": "f_abc123def456xyz0", "current": 5000, "limit": 5000, "upgrade_url": "https://gopigeon.dev/dashboard/billing", "queued": false, "will_replay_on_upgrade": false }
Note queued: false. Over-quota form submissions are auto-enqueued and replayed to your destinations when you upgrade, because an over-quota lead is a real message someone is waiting for. A telemetry endpoint has no destination to replay to, so holding the event would preserve nothing — the honest answer is to say it wasn’t stored.
Reading events back
Telemetry is read through the same submissions API as everything else. Use the since cursor rather than paginating from the top — a nightly full walk gets more expensive every night, which is exactly the cost telemetry volume makes painful.
# Incremental pull: everything changed since the last watermark. curl -s -H "Authorization: Bearer $GOPIGEON_API_KEY" \ 'https://api.gopigeon.dev/api/forms/f_abc123def456xyz0/submissions?since=2026-08-01T00:00:00Z'
The response carries next_since — store it and pass it back on the next pull. Because the cursor runs over updated_at, it catches edits to existing rows as well as new events.