Push notifications
Send push notifications to people's iOS and Android devices through APNs and Firebase Cloud Messaging.
Voidhash sends push notifications to people's devices through Apple Push Notification service (APNs) and Firebase Cloud Messaging (FCM). You connect your push credentials, your app registers each device's push token, and you send from an automation, your backend, the CLI or an AI agent.
Push notifications are in early access. Ask us on Discord, or email support@voidhash.com on a paid plan, to turn them on for your team.
Studio has no screen for writing a one-off push. To message people from Studio, build an automation with a Send push notification step.
Connect a push service
Open Settings → Notifications and click Set up next to the service you use. A service delivers nothing until its credentials are saved and Enabled is on.
Apple Push Notification service delivers to iOS devices with a token-based auth key.
| Field | What to enter |
|---|---|
| Team ID | Your 10-character Apple Developer Team ID. |
| Key ID | The Key ID of your APNs auth key. |
| Bundle ID | Your app's bundle identifier, such as com.example.app. |
| Environment | Production or Sandbox: which APNs servers to use when a device doesn't say. |
| Private key (.p8) | The contents of the .p8 file Apple gave you when you created the key. |
Firebase Cloud Messaging delivers to Android devices, and to iOS apps that use Firebase Messaging.
| Field | What to enter |
|---|---|
| Firebase project ID | The Firebase project that owns the app. |
| Service account JSON | A service account key from the Firebase console, pasted whole. |
| Android priority | High or Normal. |
| Android TTL | Optional. How long FCM keeps trying, such as 3600s. |
| APNs priority | 10 or 5, for iOS devices reached through FCM. |
Saved keys are encrypted and never shown again. Leave a key field blank to keep the saved one. To stop sending through a service without losing its credentials, turn Enabled off. Delete it from the ⋮ menu.
From the CLI, voidhash push config set apns --team-id … --key-id … --key-file AuthKey.p8 --bundle-id …
and voidhash push config set fcm --service-account-file … do the same. See
CLI commands.
Register devices
A person receives a push only on devices your app has registered for them. Each registration ties one platform push token to the distinct ID the app sends.
No Voidhash SDK registers a device for you, including when a paywall or flow asks for notification permission. Your app gets the push token from the platform and sends it to Voidhash itself. Register devices for push has the app code for Swift, Kotlin and React Native.
The app calls POST /api/v1/sdk/push-devices/register, a client API endpoint. It takes the
publishable key and the same headers the SDKs send,
and returns a pushDeviceTokenId that the app keeps:
- When the platform rotates the token, the app calls
POST /api/v1/sdk/push-devices/refresh. - When the person signs out, it calls
POST /api/v1/sdk/push-devices/unregisterbefore the SDK signs out.
A device belongs to the person who registered it last. When APNs or FCM reports that a token no longer exists, Voidhash stops sending to it. Push device API lists every header, field and response.
Send a push
Sending reaches real people and can't be taken back. It needs the admin or owner role.
| From | How |
|---|---|
| Studio | An automation with a Send push notification step. |
| HTTP API | POST /api/v1/projects/{projectId}/push-notifications with a secret key. |
| Server libraries | notifications.send in PHP, Go and Rust; pushNotifications.create in Node.js. |
| CLI | voidhash push send --to user_123 --title "…" --body "…". Shows who it reaches and asks first. |
| MCP | send_push_notification. The agent shows you the recipients and sends only after you agree. |
curl https://api.voidhash.com/api/v1/projects/$VOIDHASH_PROJECT_ID/push-notifications \
-H "Authorization: Bearer vh_sk_..." \
-H "Idempotency-Key: trial-ending-user_123-2026-11-05" \
-H "content-type: application/json" \
-d '{
"distinctIds": ["user_123"],
"title": "Your trial ends tomorrow",
"body": "Keep premium for $4.99 a month.",
"data": { "screen": "paywall" }
}'The API answers 202 once the send is accepted and delivers in the background. It goes to every
registered device of each person. Distinct IDs that match no person come back in
unresolvedDistinctIds instead of failing the request. Send again with the same Idempotency-Key
and you get the first send back, so retries never notify anyone twice.
| Field | Description |
|---|---|
personIds, distinctIds | Who to notify. At least one person is required. |
title, body | Required. |
data | Key-value data your app receives with the notification. |
sound, badge | The sound to play and the app icon badge number. |
priority | default or high. |
ttlSeconds | How long the push service keeps trying to deliver. |
channelId | The Android notification channel. |
collapseId | Notifications with the same collapse ID replace each other on a device. |
Voidhash adds a voidhash_send_id key to data so the SDKs can report opens. A send fails with
push_notifications_not_configured when no push service is enabled. Devices of a service that isn't
enabled are skipped.
Follow delivery
Activity → Sent notifications lists every send with its status and counts. Open one to see the
message, its idempotency key and each device's delivery. The CLI has voidhash push list and
voidhash push deliveries <push>.
| Send status | Meaning |
|---|---|
pending | Accepted, not yet started. |
in_progress | Deliveries are still going. |
succeeded | Every device accepted it. |
partial_failed | Some devices accepted it and some failed. |
failed | No device accepted it. |
no_recipients | The people named have no registered device on an enabled service. |
Each device's delivery is pending, in_progress, succeeded, failed or exhausted. A delivery
that fails with a temporary error is tried up to 5 times, waiting 10 seconds, 30 seconds, 5 minutes
and 30 minutes between attempts, or as long as the push service asks. exhausted means it gave up
after the last attempt.
"Succeeded" means APNs or FCM accepted the notification, not that the person saw it.
Measure opens
Call trackPushOpened when someone opens a notification, so Voidhash records a $push_opened event
for that send. Each SDK page shows where to call it:
Swift,
Kotlin and
React Native.
Studio's push report then shows delivery, opens and revenue after a push; see Push reports.
Next steps
- Automations: send pushes when people do something, or on a schedule.
- Register devices for push: app code for each platform.
- People and identity: how distinct IDs map devices to people.
- CLI commands:
push send,push listandpush config.
Something wrong or missing on this page? Tell us by email or on Discord.