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 the identifiers below.
external_idstringConditionalYour own user id — the one the mobile and web SDKs identify with. Tried first, before email and 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 any of external_id, email and phone_number — they are tried in that order. 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.
icon_urlstringNoSmall icon. 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, 60–2419200 (28 days, FCM's maximum).
prioritystringNohigh or normal.
action_buttonsobject[]NoUp to 3 tappable buttons.
data_payloadobjectNoFlat string→string map delivered alongside the notification.

Each action_buttons entry:

FieldTypeRequiredDescription
action_idstringYesStable id, 1–64 characters. Echoed back on the click engagement so you can tell buttons apart.
labelstringYesButton text, 1–64 characters.
deep_linkstringNoOpened when this button is tapped. Max 2048 characters.
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.
503concurrent_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 $ARSEL_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."
}