Carillon docs

Send a notification

Send from your backend and read the response and delivery results.

Prerequisites

  • A Carillon app with a valid provider credential. See quickstart.
  • A secret key for that app.
  • A device registered by the SDK, with identify() called so it carries your own user identifier as external_id. The examples use user-42; replace it with one of yours.

Samples on this page show placeholders. Sign in to the dashboard in this browser and they fill in with your own organization, app and mobile key.

1. Send

Target the user identifiers your backend already holds with audience.external_ids. Every device that carries one of them receives the notification; unknown identifiers are ignored.

Use a new Idempotency-Key for each intended notification. Reuse it with the same request body when retrying after a timeout. The endpoint returns 202 Accepted.

curl -X POST https://api.carillon.dev/v1/messages \-H "Authorization: Bearer carillon_sk_live_your_secret_key" \-H "Content-Type: application/json" \-H "Idempotency-Key: 018f2c8a-6f3f-7a2b-9f1e-2c9b0f4d5a11" \-d '{  "audience": { "external_ids": ["user-42"] },  "payload": {    "title": "Your order has shipped",    "body": "Track it from the app.",    "data": { "order_id": "1234" },    "localizations": {      "fr": { "title": "Votre commande est partie", "body": "Suivez-la depuis votre application." }    }  }}'
{
  "id": "01937b20-0000-7000-8000-000000000000",
  "status": "sending",
  "mode": "live",
  "created_at": "2026-08-20T09:14:03.221Z",
  "deliveries": [],
  "warnings": []
}

deliveries is empty here: the devices behind external_ids are resolved by the worker after the request is accepted. Read the trace for the per-device results.

Read the warnings

warnings is empty on an ordinary send. An entry means part of this campaign cannot go out and was written off before anything was attempted because a provider credential is missing or unusable. A campaign where every platform is in that state is refused with 422 instead of accepted.

2. Read the trace

curl https://api.carillon.dev/v1/messages/01937b20-0000-7000-8000-000000000000 \-H "Authorization: Bearer carillon_sk_live_your_secret_key"
{
  "id": "01937b20-0000-7000-8000-000000000000",
  "status": "sending",
  "schedule_type": "immediate",
  "delivery_rate": 100,
  "remaining": 0,
  "paused_reason": null,
  "outcomes": { "sent": 1, "failed": 0, "pending": 0, "total": 1 },
  "deliveries": [
    {
      "id": "01937b21-0000-7000-8000-000000000000",
      "device_id": "01937b1e-0000-7000-8000-000000000000",
      "status": "sent",
      "error_code": null,
      "opened_at": null
    }
  ]
}

outcomes covers the whole audience. deliveries is a bounded sample of the first fifty, in creation order, so a campaign of a million does not answer with a million rows.

opened_at stays null until a handset reports opening the notification. A notification nobody opened and one opened by an app that never reports look the same here.

Send to device IDs

When you hold Carillon device IDs, from deviceId() in the SDK, the dashboard or an export, name them directly. The 202 response then lists the delivery created for each device, including the ones that will fail.

curl -X POST https://api.carillon.dev/v1/messages \-H "Authorization: Bearer carillon_sk_live_your_secret_key" \-H "Content-Type: application/json" \-H "Idempotency-Key: 018f2c8a-6f3f-7a2b-9f1e-2c9b0f4d5a12" \-d '{  "audience": { "device_ids": ["01937b1e-0000-7000-8000-000000000000"] },  "payload": { "title": "Your order has shipped", "body": "Track it from the app." }}'
{
  "id": "01937b22-0000-7000-8000-000000000000",
  "status": "sending",
  "mode": "live",
  "created_at": "2026-08-20T09:15:11.004Z",
  "deliveries": [
    { "id": "01937b23-0000-7000-8000-000000000000", "device_id": "01937b1e-0000-7000-8000-000000000000" }
  ],
  "warnings": []
}

The four audience shapes

AudienceShapeLimit
Your user identifiers{ "external_ids": ["…"] }1 to 2000, each at most once; unknown identifiers are ignored
Named devices{ "device_ids": ["…"] }1 to 100, each at most once
Everyone{ "all": true }The whole app
A saved audience{ "audience_id": "…" }See audiences

Only a device_ids send returns its deliveries in the 202 response. For the three other shapes the list is empty: the audience is resolved by the worker, so the identifiers are minted then.

Register a token without an SDK

The SDKs do this for you. Doing it by hand is useful when you are testing the API itself, or when you already hold tokens from somewhere else.

curl -X POST https://api.carillon.dev/v1/devices \-H "Authorization: Bearer carillon_mk_live_your_mobile_key" \-H "Content-Type: application/json" \-d '{  "token": "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90",  "platform": "ios",  "environment": "sandbox",  "external_id": "user-42",  "tags": { "plan": "pro" },  "timezone_id": "Europe/Paris",  "locale": "fr-FR",  "opted_in": true}'

200, with the device as it now stands. Registration is an upsert on the token, so running this again with a changed external_id updates the same row rather than creating a second one.

Replace the example token with a real APNs or FCM token. Keep the returned id for the send request.

Target an audience · Delivery · Errors

On this page