Sending notifications
POST /api/v1/notifications: fields, targeting, scheduling, A/B tests, merge tags and cancelling a scheduled send.
POST /api/v1/notifications — accepts the send and answers 202; delivery happens in the background. With no targeting fields it goes to every subscriber of the website the API key belongs to. Until the account's email address is verified it answers 403 email_not_verified (test sends to your own test devices still work).
curl -X POST https://relaybell.com/api/v1/notifications \
-H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
-d '{
"title": "Flash sale 🔥",
"body": "30% off ends tonight — {name|there}!",
"url": "https://shop.example/sale"
}'Response (202):
{ "ok": true, "notificationId": "5b0c2f7e-1d7a-4c3e-9a51-2f8e6b1d0c42", "status": "queued" }status is "queued" when sending starts now, or "scheduled" when it waits — for a future sendAt, or because your quiet hours are on (the send is held until they end). A send to everyone from an account less than 7 days old that would reach more than 5,000 browsers answers "held_for_review" with a message: our team checks a new account's first large sends before they go out (usually within a working day), and you can cancel it meanwhile. A narrower broadcast from such an account can be held the same way just before it goes out. Sends to specific people are never held. Keep notificationId: it is how the send appears in /stats and in webhooks, and how you cancel it.
| Field | Description |
|---|---|
title * | Notification title. Required — without it the answer is 400 title_required. Supports merge tags such as {name|there} (the subscriber's name tag, or the text after | when it's empty). |
body | Message text. Supports merge tags. |
url | Page opened when the notification is clicked. Left out, your Default link (Settings → Sending → Links) is used. Your UTM tags, if set, are added. Supports merge tags. |
icon | Small image (URL) shown next to the text. Left out, your Default notification logo (Settings → General) is used. |
image | Large picture (URL). Shown mainly by Chrome and Edge on Windows and Android; Safari, Firefox and Macs leave it out. |
badge | Small one-colour icon (URL of a PNG with a transparent background, about 96×96 px) shown in the Android status bar. |
requireInteraction | true keeps the notification on screen until the person clicks or closes it, instead of it fading after a few seconds. Chrome and Edge on computers honour it; Safari and Firefox ignore it. |
tag | A label of your choice. A new notification with the same tag replaces one that is still showing instead of stacking beside it — useful for updates like “Your order is out for delivery”. |
actions | Up to 2 buttons: [{action,title,url}]. Each needs a title; action defaults to btn1/btn2; url is kept only when it is an http(s) link. |
externalId | Send to one of your own users: the user id you gave the browser SDK (externalId in init(), or identify()). It reaches every subscribed browser linked to that id. The id must match exactly (case matters). See Send to one user. |
externalIds | The same for a list: ["user_1", "user_2"]. Used instead of externalId when both are sent. If every id is empty, the answer is 400 external_ids_empty — it never falls back to sending to everyone. |
transactional | true for a message a person is waiting for (order update, sign-in alert, a reply): it goes out straight away even during your quiet hours, skips the daily frequency cap, and is never listed in the public on-site inbox (showInbox()). Use it for messages to particular people, not for marketing. |
segmentId / filters | Target a saved segment, or inline filters in the segment filter format. A segment that isn't in this project answers 400 segment_not_found; one deleted before a scheduled send goes out makes that send fail (error: "segment_missing") — it never falls back to everyone. Only one way of choosing people is used per send: segmentId first, then filters, then externalIds/externalId — so don't combine them. |
topicId | Only subscribers of a topic — its id or its slug (e.g. "tech-news", any case). Combines with the fields above. An unknown topic answers 400 unknown_topic. |
excludeSegmentId / excludeFilters | Leave a segment (or the people matching inline filters) out of this send. excludeSegmentId wins when both are sent; an unknown one answers 400 segment_not_found. |
sendAt | ISO 8601 time to schedule the send (status "scheduled"). A time in the past, or one that can't be read, sends now. A send with sendAt isn't moved by quiet hours. |
perTimezone + localTime | Deliver at a local time ("H:MM" or "HH:MM", e.g. "09:00") in each subscriber's time zone. sendAt is ignored, and without a valid localTime it sends normally. |
smartTime | Deliver at each subscriber's most-active hour. sendAt is ignored. |
ttl | How long (seconds) the push service keeps trying to reach a device that is offline. Default 24 hours, maximum 28 days. |
translations | { "es": {title,body}, ... } — keyed by two-letter language, picked by each subscriber's browser language; others get the main text. |
ab | A/B test: { "b": { title, body, url, image } } — each browser gets version A or B (fields left out of B come from A). Ignored unless b has at least one field. |
abTest | Auto-winner, with ab: { autoWinner:true, testPercent:20, afterHours:4 } — sends both versions to testPercent% (1–90, default 20), then after afterHours (1–168, default 4) sends the version with the higher click rate to the rest (with no clicks yet, the rest get the normal A/B split). |
Merge tags (personalization)
Write {key} or {key|fallback} in a send's title, body or url, and each subscriber gets their own value. An empty or unknown value becomes the fallback (the text after |), or nothing when there is none. Keys are letters, digits, _, . and -, and are case-sensitive.
| Tag | Value |
|---|---|
{external_id} | The user id linked to the subscriber (externalId). |
{url} | The last page of your site the subscriber was on when they subscribed or were last updated. |
{language} | The browser's language, e.g. en-GB. |
{country} | The subscriber's 2-letter, upper-case country code (ISO 3166-1, e.g. IN), worked out from the visitor's IP address when the browser subscribes or is saved again; empty when unknown. It can't be set from the SDK — use a tag for that. |
{KEY} | Any of the subscriber's tags (tags in init(), setTags(), an import or a bulk label), e.g. {name|there} or {plan}. A tag with the same name as a built-in one wins. |
Tags are filled in only when the main title, body or url contains one; then the A/B version B and the translations are personalized too. In automation, journey and product alert messages the same {key|fallback} syntax takes its values from the event's properties instead (for example {product}, {url}) — not from the subscriber.
Cancel a scheduled send
POST /api/v1/notifications/:id/cancel — stops a send that is still "scheduled" (a future sendAt, or one held for quiet hours) or "held_for_review". Answers 200 { "ok": true, "canceled": true }. A send that has already started (including a per-time-zone or smart-time send, which starts at once), has finished, was already canceled, doesn't exist or belongs to another website answers 404 { "error": "not_scheduled" }.
curl -X POST https://relaybell.com/api/v1/notifications/5b0c2f7e-1d7a-4c3e-9a51-2f8e6b1d0c42/cancel \
-H "Authorization: Bearer rb_live_xxxxx"