Skip to main content
Tassyir tells your app what happens on the businesses that installed it: an order created, confirmed or delivered, a product changed, stock moved, the app uninstalled. Each event is a signed HTTPS 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/redact and bulk/finished reach 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:
  1. the v1a signature, against the keys of GET 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;
  2. the timestamp, within 5 minutes of now;
  3. the envelope’s appId (your numeric app id, TASSYIR_APP_ID: not the app_… client id) and installationId (your check);
  4. 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), and orders/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-id only 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 updatedAt with 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/updated is 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), with changed naming the order’s top-level fields. A write that also changed fields outside its topic’s own sends orders/updated for those too.
  • orders/created is sent once per real order. An abandoned checkout is abandoned_checkouts/created; when it becomes an order (same id), orders/created is sent then.
  • A returned order keeps its status (usually pending): read the returned flag.
  • Carriers. A carrier’s status change is orders/updated with changed naming delivery (and status when the order’s status moved with it). A carrier update that changes nothing sends nothing.
  • Products. products/updated names the product’s top-level fields that changed (price, variants, images, active, collectionIds, …). A deactivated product is products/updated with active: false: no product is ever deleted, so products/deleted is not sent today.
  • Stock. inventory_levels/updated is sent once per stock item per change, with changed empty: the body is the item’s whole stock. Stock can be negative after a dispatch shortfall; it is sent as it is.
  • Customers. customers/updated is 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 header tassyir-test: true, a webhook-id of dlv_test_… and the envelope id evt_0; its businessId, appId and installationId are real. It is never retried, never logged, and never disables your endpoint. @tassyir/app refuses test deliveries (WEBHOOK_TEST_DELIVERY) unless you pass testDeliveries: 'accept'; then delivery.test is 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-id and 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.
A delivery’s status is pending, delivered, failed or skipped. The error codes the log shows: