Skip to main content

Send Push

Send a transactional push notification to a single contact's registered devices.

  • Order and shipping updates
  • Delivery and booking reminders
  • Account and security alerts

Endpoint

POST /push/send

Returns: 202 Accepted

Prerequisites

Push must be configured for your organization

Before any notification can be delivered you must complete push setup in the Arsel Dashboard under Integration > Push, for each platform you ship on: a Firebase service account for Android, an APNs auth key (.p8) for iOS, and Web Push for browsers. iOS goes to Apple directly — there is no Firebase in that path. Because the sending identity stays yours, device tokens you already hold keep working. See Setting up push.

Devices are registered by the client SDKs or via Register Device. This endpoint addresses a contact, never a device token — an anonymous registration is stored but is not addressable until it is bound to a contact.

Headers

HeaderValueRequired
AuthorizationBearer <your-api-key>Yes
Content-Typeapplication/jsonYes
Idempotency-KeyAny unique string, max 256 charsNo — see Idempotency

Body Parameters

Identifying the contact

ParameterTypeRequiredDescription
contact_idstringConditionalArsel contact UUID. Mutually exclusive with email/phone_number.
emailstringConditionalContact email. Matched case-insensitively; authoritative when both email and phone_number are given.
phone_numberstringConditionalContact phone in E.164 format (e.g. +966501234567).
Identify the contact

Provide contact_id on its own, or one or both of email and phone_number. Combining contact_id with the others, or providing no identifier, returns 400. This is the same contact reference shape Send Event accepts.

Notification content

ParameterTypeRequiredDescription
titlestringYesNotification title. 1–200 characters.
bodystringYesNotification body. 1–1000 characters.
image_urlstringNoLarge image. HTTPS only.
deep_linkstringNoOpened when the notification is tapped. Max 2048 characters.
android_channel_idstringNoAndroid notification channel id. Max 128 characters.
categorystringNoAnalytics label, e.g. otp or order_update. Max 64 characters.
ttl_secondsintegerNoHow long delivery may be retried, 602419200 (28 days, FCM's maximum).
prioritystringNohigh or normal.
action_buttonsobject[]NoUp to 3 tappable buttons.
data_payloadobjectNoFlat string→string map delivered alongside the notification.
Reserved data keys

data_payload must be a flat map of strings to strings. Keys beginning arsel_, google, or gcm, and FCM's own reserved keys, are rejected — Arsel uses that namespace for the metadata that makes delivery and engagement tracking work.

{
"email": "john.doe@example.com",
"title": "Your order shipped",
"body": "Order A-1023 is on its way — arriving Thursday.",
"deep_link": "myapp://orders/A-1023",
"category": "order_update",
"ttl_seconds": 86400,
"data_payload": {
"order_id": "A-1023"
}
}

Response

The notification is accepted for asynchronous delivery. Every ACTIVE device the contact has registered receives it.

{
"id": "01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"
}
FieldTypeDescription
idstringMessage identifier (UUIDv7). Use it to track delivery in push analytics.

A 202 means Arsel accepted the request, not that a device displayed the notification. Delivery and engagement are reported afterwards by the SDK on the device.

A contact who is reachable but currently has no active device is a successful skip, not an error — you still get a 202, and the per-device outcome appears in push analytics.


Idempotency

Sending the same notification twice — for example because a network timeout made you retry — could send the notification twice to the same device. To make a retry safe, send an Idempotency-Key header whose value is unique to that notification (for example order.shipped/A-1023):

  • The first request with a given key is processed normally and its response is stored.
  • Any later request with the same key (within 24 hours) is not reprocessed — it returns the original response unchanged, plus an Idempotent-Replayed: true response header. The side effect happens only once.
  • After 24 hours the key expires and may be reused.

The key can be any unique string up to 256 characters. A UUID works, but a value derived from the entity the request is about — like order.shipped/A-1023 — is easier to regenerate identically on a retry. Keys are scoped per endpoint, so the same value can be reused independently across the send-email, send-SMS, and send-event endpoints without colliding.

Reusing a key with a different payload

Reuse a key with a different request body and the request is rejected with 409 invalid_idempotent_request, which protects you from receiving the wrong cached response. Use a fresh key for a genuinely different request.

Idempotency error responses

StatusnameWhen
400invalid_idempotency_keyThe key is empty or longer than 256 characters.
409invalid_idempotent_requestThe key was already used with a different request body.
409concurrent_idempotent_requestsAn earlier request with the same key is still being processed — retry shortly.

Examples

curl -X POST "https://api.arsel.sa/v1/push/send" \
-H "Authorization: Bearer be_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order.shipped/A-1023" \
-d '{
"email": "john.doe@example.com",
"title": "Your order shipped",
"body": "Order A-1023 is on its way.",
"deep_link": "myapp://orders/A-1023"
}'

Error Responses

Provide contact_id on its own, or one/both of email and phone_number:

{
"status_code": 400,
"name": "bad_request",
"message": "Provide at least one of contact_id, email, or phone_number."
}

Combining contact_id with the others returns:

{
"status_code": 400,
"name": "bad_request",
"message": "Provide contact_id on its own, or one/both of email and phone_number — not contact_id together with them."
}