Send Notification
Dispatch a push notification to a device token, all devices for a user, or a topic. Control priority and time-to-live, attach custom data, and receive a pushId for webhook correlation.
Device targeting
Address a single FCM or APNs token when you already know the handset.
User fan-out
Send once with type:user — every registered device for that userId receives the push.
Topic broadcast
Reach all devices subscribed to a topic without maintaining recipient lists.
Rich + data
Visible title/body/image plus silent data keys for deep links and in-app routing.
Authentication
Authorization: Basic <token>. Auth guide →POST
/v1/push/send
<200ms
95th percentile
200/s
per API key
86400s
24 hours
Request body
{"to": {"type": "user","value": "user_abc123"},"title": "Payment received","body": "GHS 250.00 was credited to your wallet.","data": {"type": "wallet_credit","amount": "250.00","currency": "GHS","screen": "wallet"},"image": "https://cdn.example.com/push/wallet.png","priority": "high","ttl": 3600}
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| to | object | required | Target descriptor: { type, value }. |
| to.type | "device" | "user" | "topic" | required | How to resolve recipients. |
| to.value | string | required | Device token, userId, or topic name (e.g. news). |
| title | string | required | Notification title. Max 128 characters. |
| body | string | required | Notification body. Max 1024 characters. |
| data | object | optional | Custom key/value map for the app. Not always shown in the system UI. |
| image | string (URL) | optional | HTTPS URL of an image for expanded notification views. |
| priority | "high" | "normal" | optional | Default high. Use normal for non-urgent marketing to preserve battery. |
| ttl | integer | optional | Seconds the platform may store the message if the device is offline. Default 86400. |
Targeting examples
{"to": {"type": "device","value": "dG9rZW4tZXhhbXBsZS1mY20tYW5kcm9pZA..."},"title": "Security alert","body": "A new login was detected on your account.","priority": "high"}
Resolves to exactly one registered token. Returns 404 DEVICE_NOT_FOUND if the token was never registered or was deleted.
Priority & TTL
| Field | Values | Guidance |
|---|---|---|
| priority | high (default), normal | Use high for OTPs, security, and time-critical alerts. Use normal for promos so the OS may batch delivery and save battery. |
| ttl | 1 – 2,419,200 seconds (28 days) | Short TTL for flash sales or OTPs (e.g. 300–3600). Longer for content that remains useful offline. After TTL, undelivered messages are dropped and may surface as expired. |
Response
{"success": true,"code": "PUSH_QUEUED","message": "Push notification accepted for delivery.","requestId": "REQ-1782000001001","timestamp": "2026-07-12T11:20:00.000Z","data": {"pushId": "PUSH-8c1a2b3d-4e5f-6789-abcd-ef0123456789","channel": "push","business": {"id": "BU-VOIF86X7","name": "Letcol Technologies"},"target": {"type": "device","value": "dG9rZW4tZXhhbXBsZS1mY20...","platform": "android","resolvedDevices": 1},"notification": {"title": "Payment received","body": "GHS 250.00 was credited to your wallet.","image": "https://cdn.example.com/push/wallet.png","data": {"type": "wallet_credit","screen": "wallet"}},"delivery": {"status": "queued","priority": "high","ttl": 3600,"submittedAt": "2026-07-12T11:20:00.000Z"},"billing": {"units": 1,"pricing": {"unitCost": 0.002,"totalCost": 0.002,"currency": "USD"}}}}
Code examples
curl -X POST 'https://api.sendexa.co/v1/push/send' \-H 'Content-Type: application/json' \-H 'Authorization: Basic YOUR_DASHBOARD_BASE64_TOKEN' \-d '{"to": { "type": "user", "value": "user_abc123" },"title": "Payment received","body": "GHS 250.00 was credited to your wallet.","data": { "screen": "wallet" },"priority": "high","ttl": 3600}'
Sendexa maps your REST body to native FCM and APNs fields. You do not need to craft platform-specific JSON unless you have advanced needs (contact support for custom APNs headers).
| Sendexa field | FCM (Android) | APNs (iOS) |
|---|---|---|
| title | notification.title | aps.alert.title |
| body | notification.body | aps.alert.body |
| image | notification.image / android.notification.image | mutable-content + attachment (when supported) |
| data | data (string map) | custom root keys alongside aps |
| priority: high | android.priority = HIGH | apns-priority: 10 |
| priority: normal | android.priority = NORMAL | apns-priority: 5 |
| ttl | android.ttl | apns-expiration |
| Scope | Limit | Headers |
|---|---|---|
| Per API key | 200 requests / second | X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset |
| Burst | Short bursts up to 400/s may be absorbed; sustained overage returns 429 | Retry-After on 429 |
| Topic fan-out | Counts as 1 API request; delivery fan-out is asynchronous | — |
429 handling
RATE_LIMIT_EXCEEDED, back off exponentially (e.g. 250ms → 500ms → 1s → 2s) and respect Retry-After when present. Do not retry failed validation (4xx other than 429) without fixing the payload.| HTTP | Code | When |
|---|---|---|
| 400 | INVALID_TARGET | Missing or invalid to.type / to.value |
| 400 | INVALID_TOKEN | Device target token format rejected |
| 400 | TITLE_REQUIRED | title missing or blank |
| 400 | BODY_REQUIRED | body missing or blank |
| 400 | PAYLOAD_TOO_LARGE | Serialized notification exceeds size limits |
| 400 | INVALID_IMAGE_URL | image is not a valid HTTPS URL |
| 400 | INVALID_TTL | ttl outside allowed range |
| 401 | UNAUTHORIZED | Auth header missing or invalid |
| 403 | PUSH_CREDENTIALS_MISSING | No FCM/APNs credentials for resolved platform(s) |
| 403 | INSUFFICIENT_CREDITS | Balance too low for resolvedDevices units |
| 404 | DEVICE_NOT_FOUND | type=device and token not registered |
| 404 | USER_HAS_NO_DEVICES | type=user with no registered devices |
| 404 | TOPIC_EMPTY | type=topic with zero subscribers |
| 429 | RATE_LIMIT_EXCEEDED | Sustained over 200 req/s |
- API returns
queuedwith apushId. - Sendexa hands off to FCM/APNs → status
sent(internal / webhook depending on provider feedback). - Platform confirms delivery when available →
push.delivered. - User taps notification (app reports open) →
push.opened. - Permanent failure (invalid token, auth, etc.) →
push.failed; unregister dead tokens.
Full webhook payloads are documented under Webhooks.
Best practices
- Persist
pushIdwith your business event (order id, invoice id) for support and analytics. - Prefer
type: userafter login so multi-device users stay in sync without token bookkeeping in every service. - Put navigation hints in
data(e.g.screen,entityId) rather than only in the body text. - Host images on a fast HTTPS CDN; large images delay display on some devices.
- Avoid sending high-priority marketing at scale — OS throttling and user opt-outs increase over time.
- Idempotency: if your client retries on network failure, use your own dedupe key in
dataso the app can ignore duplicates.