POST to your server, sent at
least once and in no particular order. Verify every delivery, answer fast,
and handle each one once.
Subscribe
List the topics in the manifest, each with the address that receives it:uri is a path on your hosting.url, or an absolute https URL. A topic is
subscribed once in the whole manifest, and needs the scope that reads its
resource. A new topic takes effect on an installation once its merchant
accepts the version that adds it.
Topics
app/*,business/redactandbulk/finishedreach only the installation they are about, never another app. Every other topic reaches each active installation of the business that subscribes to it and was granted its scope.- An installation limited to selected stores receives order, checkout, product and stock events of its stores only. Customer events carry that installation’s stores’ counters.
- The resources (
order,product, …) are the same as the Admin API’s (Orders, Products and collections, Stock, Customers), masked the same way.
What you receive
The body is the envelope:
id (evt_…), type, apiVersion,
createdAt (when the event happened), businessId, storeId (may be
null), appId and installationId (check both are yours), changed and
data, one key per topic.
data is read when the delivery is built, seconds after the event, so it
can be newer than the event. Reconcile by the resource’s updatedAt, and read
the resource again through the Admin API when you are unsure. Ignore fields
you do not know: a version may add fields, never rename or remove one.
Verify every delivery
Tassyir signs{webhook-id}.{webhook-timestamp}.{body} with Ed25519 (Standard
Webhooks, scheme v1a). Verify the raw body, the exact bytes you
received: parsing the JSON and serialising it again breaks the signature.
tassyir.verifyWebhook checks, in order:
- the
v1asignature, against the keys ofGET https://api.tassyir.io/apps/webhook-keys(cached an hour, fetched again when no key verifies). During a key rotation a delivery carries several signatures; any one that verifies is enough; - the timestamp, within 5 minutes of now;
- the envelope’s
appId(your numeric app id,TASSYIR_APP_ID: not theapp_…client id) andinstallationId(your check); - the unsigned
tassyir-*headers agree with the signed envelope.
delivery.event is typed per topic: switch (delivery.event.type) narrows
data. If the keys cannot be fetched once the cached copy has expired, it
throws KEYS_UNAVAILABLE: answer 503, and Tassyir delivers again later.
Without the SDK: decode each whpk_… key of the keys document (base64 of
the raw 32-byte Ed25519 public key), split webhook-signature on spaces,
keep the v1a, entries, and accept the delivery when one of them verifies
{webhook-id}.{webhook-timestamp}.{raw body} under one of the keys, the
timestamp is within 5 minutes, and appId and installationId are yours.
Answer within 10 seconds
- Answer 2xx as soon as the delivery is verified and recorded; do slow work afterwards, from a queue. Tassyir does not read your answer’s body.
- Tassyir waits 10 seconds for the connection and your answer’s headers, and follows no redirect: a 3xx is a failed attempt.
- Your endpoint must be https on port 443, at a public hostname.
Retries, and disabled endpoints
- Three days of continuous failure disables the installation’s webhooks, and Tassyir emails you, at most once a day per app. Any 2xx resets the clock.
- Disabling is per installation, for all its topics. Until you re-enable it in the developer console, its events are recorded as skipped and never sent. Fix your endpoint first: a 410 or three more days of failure disables it again. Then replay the skipped deliveries that matter.
- A delivery still pending four days after its event fails.
Handle each event once
Delivery is at least once, in no particular order. The same delivery can arrive twice (a retry after your answer was lost, a replay), andorders/delivered can arrive before orders/dispatched.
- Deduplicate on
webhook-id: it is the same on every retry and replay of a delivery. Keep the ids for at least a week (replays are possible for 7 days). - Record a
webhook-idonly once its event is handled, or saved to a durable queue that will handle it. An id recorded before a failed attempt makes the retry look handled, and the event is lost. Two copies of one delivery can still race: make what an event does safe to do twice. - Never rely on order: compare the resource’s
updatedAtwith what you hold, and read it again when unsure. - An event for an installation you do not know yet (it overtook
app/installed) can be refused with a 4xx: it is retried.
How events are recorded
- One fact, one event. Confirming, cancelling, dispatching, delivering or
returning an order sends that topic.
orders/updatedis sent for every other change an app can read (the buyer, the items, the delivery and the carrier’s status, scheduling, archiving, a waiting order unlocking), withchangednaming the order’s top-level fields. A write that also changed fields outside its topic’s own sendsorders/updatedfor those too. orders/createdis sent once per real order. An abandoned checkout isabandoned_checkouts/created; when it becomes an order (same id),orders/createdis sent then.- A returned order keeps its status (usually
pending): read thereturnedflag. - Carriers. A carrier’s status change is
orders/updatedwithchangednamingdelivery(andstatuswhen the order’s status moved with it). A carrier update that changes nothing sends nothing. - Products.
products/updatednames the product’s top-level fields that changed (price,variants,images,active,collectionIds, …). A deactivated product isproducts/updatedwithactive: false: no product is ever deleted, soproducts/deletedis not sent today. - Stock.
inventory_levels/updatedis sent once per stock item per change, withchangedempty: the body is the item’s whole stock. Stock can be negative after a dispatch shortfall; it is sent as it is. - Customers.
customers/updatedis sent when a customer reads differently, never when it was only recomputed.
Test, replay and read the log
In the developer console:- Send a test (the Installs tab): a synthetic event of one topic your
installation subscribes to, with sample data (order
TEST-1001, buyer “Test Buyer”), signed and sent like a real delivery. It is marked by the headertassyir-test: true, awebhook-idofdlv_test_…and the envelope idevt_0; itsbusinessId,appIdandinstallationIdare real. It is never retried, never logged, and never disables your endpoint.@tassyir/apprefuses test deliveries (WEBHOOK_TEST_DELIVERY) unless you passtestDeliveries: 'accept'; thendelivery.testis true. - Read the log (the Webhook deliveries tab): every delivery of the last 30 days, metadata only (status, HTTP status, error, duration, attempts), never a body.
- Replay a delivery: one more attempt now, with the same
webhook-idand a body rebuilt from the resource as it is now. A failed replay is final (no retry follows). A delivery can be replayed while its event is kept, 7 days.
status is pending, delivered, failed or skipped. The
error codes the log shows: