Skip to main content

Push device tokens and scheduled sends

Reading order matters here. Push integration is the umbrella: it takes you through the API loop from credential setup to engagement acks — register tokens, send, read per-device results, wire push.delivered / push.opened. This page is the data plane that loop runs against — the two registries it owns:
  • the device-token registry (/push/device-tokens — plus /push/voip-devices for call-push tokens), and
  • the scheduled-send queue (/push/scheduled) that a send_at on /push/send enqueues into.
Use the umbrella to get your first notification out; use this page when you are operating what it creates — token inventory hygiene, the scheduling contract, and reading results back.

1. Token transports: alert, web, and VoIP

One registration call, three transports. POST /api/v1/push/device-tokens upserts on the token string — re-registrations refresh, they never duplicate:
The web subscription endpoint is validated against SSRF — it must resolve to a public HTTPS URL, else 422 INVALID_PUSH_ENDPOINT. VoIP tokens are a separate registry. A call-push token (iOS PushKit, or Android’s high-priority call channel) goes to POST /api/v1/push/voip-devices, not the device-token registry. Keeping the two apart prevents a marketing send from ever addressing a VoIP entitlement:

2. The registry’s guarantees

  • Idempotent upsert. Registration is keyed on the token string: re-registering the same token refreshes last_seen_at and upserts the mutable fields. Register on every SDK token-refresh callback; it is safe to be noisy.
  • Install-lifecycle dedup. Pass a stable app_install_id and a reinstall that rotates the OS token retires the install’s previous row — one live token per install instead of a growing pile.
  • Provider-feedback retirement. FCM / APNs / Web Push report dead tokens back at send time (Unregistered, BadDeviceToken, 410 Gone). Those tokens are deleted from the registry on the send that discovers them — no manual sweep for the common decay case.
  • Pause vs revoke. PATCH /api/v1/push/device-tokens/:id with { "enabled": false } pauses delivery while keeping the row; DELETE retires it permanently. VoIP devices use DELETE /api/v1/push/voip-devices/:id.
  • Fail-closed gating. Every send resolves only rows in the registry — unknown device ids do not send, and disabled or suppressed tokens are filtered before the fan-out. The registry is the target resolution, not a hint.

3. The scheduled-send contract

A future send_at (ISO-8601 with offset) on POST /api/v1/push/send persists the full payload into the scheduled queue and returns 202 with the scheduled push id. Past-or-now values fall through to an immediate send — clock skew never silently eats a push. Scheduled sends are one-off; model recurring sends by enqueueing each occurrence from your own cadence (a campaign or journey step). The dispatcher claims due rows about once a minute and replays the stored payload through the same send path an immediate send takes — targeting, suppression, and frequency caps run identically. Two contract points to build against: Cancel-before-dispatch. Cancel a row with DELETE /api/v1/push/scheduled/:id while it is still scheduled. The cancel is a guarded status change, not a hard delete — once the dispatcher has moved the row to sent or failed, the cancel returns 409 SCHEDULED_PUSH_NOT_CANCELLABLE instead of silently succeeding. Race-safe by design: if your cancel and the dispatcher cross, exactly one wins and you get an honest answer either way. Failure is observable, not perpetual. A failed dispatch retries with exponential backoff (2^attempts minutes, capped); after five attempts the row lands at failed with last_error recorded. Read the row before retrying — a bad target means fix the payload and enqueue a new send; a transient blip means cancel and re-enqueue if it no longer applies. Target the broadcast or a user group the same way you would on an immediate send: user_ids: ["*"] for the whole opted-in registry (capped at 100,000 deliverable tokens — prune first, section 5), or an explicit user_ids batch. Scheduling does not change the targeting semantics, only when they resolve.

4. Worked example: register, batch a schedule, read results

Register the tokens — say one Android alert device and one browser:
Enqueue the batch — one request with a user group, held for tomorrow morning:
Read the queue and its results back:

5. Operating the registry

Reads and management writes: Writes require the owner, admin, or developer role — the same gate the dashboard’s Device Tokens and Scheduled pages apply. Prune before big sends: broadcasts resolve only enabled, unsuppressed tokens up to a 100,000-device cap. Disabling tokens from churned installs — and letting provider feedback delete the dead ones — keeps your headroom real and protects deliverability. Everything above is the data plane the push integration umbrella loops over: credential setup at the channel layer, tokens registered into this registry, sends resolved and scheduled against it, engagement acks closing the loop. When you are deciding which page to follow — the umbrella to integrate, this page to operate — that split is the whole answer.