Notifications
Tanqory sends email. Every other channel — SMS, push, LINE, anything with a carrier behind it — is yours to build. The platform's job is the event, the consent decision, the merchant's own wording, and a delivery record. Yours is getting the message to the person.
Two capabilities, declared in your manifest:
| Capability | What it does |
|---|---|
notification-channel:<name> | Adds a delivery channel alongside the platform's own |
notification-override:<type> | Sends one notification type instead of the platform |
{
"provides": ["notification-channel:sms"],
"notifications": { "dispatchUrl": "https://sms.example.com/dispatch" }
}
dispatchUrl is required as soon as provides names either capability — a
takeover with nowhere to deliver silently drops every message it claimed. It
must be plain https://, with no embedded credentials and no fragment: the
payload carries a real recipient address and rendered content.
provides asks. Staff approve at review, and a merchant's install is what
actually binds it. Until then nothing is dispatched to you.
Delegatable channels today are email and sms. Others are refused rather
than accepted and dropped.
What you receive
The platform POSTs the rendered message to your dispatchUrl, signed exactly
like every other Tanqory webhook — verify X-Tanqory-Signature the same way.
{
"version": 1,
"deliveryId": "9f0d…",
"storeId": "f2cb…",
"apiBase": "https://api-do-sgp1.tanqory.com",
"type": "order_confirmation",
"channel": "sms",
"locale": "en",
"occurredAt": "2026-08-22T04:11:07.221Z",
"idempotencyKey": "order:1042:confirmation",
"recipient": { "kind": "customer", "id": "cus_…", "address": "+66812345678" },
"content": { "subject": null, "body": null, "text": "Order 1042 confirmed…" },
"delegation": { "kind": "channel-provider", "pluginId": "…" }
}
Three things about this are deliberate and worth knowing before you build:
storeId is what you report back with. It goes in the path you POST to, and
that is how app.tanqory.com knows which regional cell holds this deliveryId.
apiBase is also on the envelope — the cell's own host, for apps written before
the store-scoped path existed. New code does not need it.
You get a rendered message, not the facts to render one. The merchant's template, their locale, and their consent and preference decisions have already been applied on our side. You do not re-decide any of it.
The address is real, not masked. A channel provider cannot text
+66******78. You only ever receive recipients that survived consent and
preference filtering — delegation sits downstream of both and is never a way
around either. That an address was shared is recorded on the delivery row.
deliveryId is your idempotency key. It is stable across retries of the
same logical send. Receiving it twice means the same message twice — send once.
What you send back
POST https://app.tanqory.com/api/v1/stores/{storeId}/apps/deliveries/{deliveryId}/status
Authorization: Bearer <your app token>
Both ids come straight off the dispatch you are answering:
await fetch(
`https://app.tanqory.com/api/v1/stores/${payload.storeId}/apps/deliveries/${payload.deliveryId}/status`,
{ … },
)
The storeId in that path is what tells app.tanqory.com which regional cell
to forward to. It is routing only — your token still decides which store you are
allowed to report on, so writing a different id there reaches a cell that has
nothing of yours in it.
{
"status": "sent",
"providerMessageId": "SM3f…",
"error": null,
"occurredAt": "2026-08-22T04:11:09.004Z"
}
status is one of sent, delivered, failed, bounced. Send error on the
last two — it is what a merchant reads in their delivery log when they ask why a
customer heard nothing.
Requires the settings.notifications_edit permission.
The response tells you what the ledger did:
{ "deliveryId": "9f0d…", "status": "SENT", "applied": true }
applied: false is not an error. It means the ledger refused the transition
— a sent report arriving after delivered, for instance. Nothing is wrong;
your report was simply later than another.
You will also see 403 if the delivery is not delegated to you, and 404 if
there is no such delivery. Neither should happen in normal operation; both mean
you are quoting an id you were not given.
Rules the platform will not bend
If you are unreachable, the platform sends. An app that is uninstalled, down or refusing does not create silence — delivery falls back to Tanqory. Never both, and never neither. Design for the possibility that a message you were expecting went out without you.
Some types are never delegated. A handful are the merchant's obligation rather than their preference, and the platform refuses to hand them over before it even looks at your binding.
Uninstalling takes effect immediately. There is no cache between the binding and the send, on purpose: a cache is a window in which an app a merchant just removed is still holding their customers' messages.
Testing it
Install your app on one of your development stores, then trigger the event —
an order, a fulfillment — the way a merchant would. The dispatch arrives at your
dispatchUrl for that store only.
There is no on-demand "send me a test dispatch" endpoint yet. Until there is, the event is the trigger.