Skip to main content

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:

CapabilityWhat 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.

A request is not a grant

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.