Skip to content
Log inStart free

Store API

Webhooks

Your systems hear about a paid order, low stock or a return the moment it happens, as a signed JSON request to your URL, so nothing has to poll the API.

Example: POST to your URL200 OK
POST /webhooks/tanqory HTTP/1.1Host: example.comContent-Type: application/jsonX-Tanqory-Webhook-Id: wh-example-7f3aX-Tanqory-Signature: t=1791561600,v1=1ca6cc7691673eabb05fd1a4dd844af79937c8540138c0981cde3c68c3537ae6X-Tanqory-Event: order_paymentX-Tanqory-Topic: orders/paidUser-Agent: Tanqory-Webhook/1.0{  "id": "evt-example-1",  "event": "order_payment",  "apiVersion": "2025-01",  "createdAt": "2026-10-09T09:20:00.000Z",  "storeId": "store-example-01",  "idempotencyKey": "order.paid:ord-example-1042",  "data": {    "storeId": "store-example-01",    "orderId": "ord-example-1042",    "orderNumber": 1042,    "customerId": "cus-example-77",    "timestamp": "2026-10-09T09:20:00.000Z"  }}

Example values: ids, times and amounts are made up. Header names, field names and the signature format are exact, and the signature is real for the example secret.

Receive

Handle a delivery

A delivery is a POST with a JSON body. Read the raw body, check the signature, skip a key you have already handled, answer 2xx at once and do the work after.

  • 2xx within 30 secondsAny status from 200 to 299 inside 30 seconds counts as delivered. Anything else is a failed attempt.
  • Not in orderEach delivery travels on its own with its own retries, so a later event can arrive first. Read createdAt, or fetch the current state from the API.
  • Dedupe on idempotencyKeyThe key comes from the business fact, such as order.paid:<orderId>, so the same fact keeps the same key on every retry.
server.js (Express)
const express = require('express')const { verifyTanqory } = require('./verify')const app = express()const seen = new Set() // keep handled keys in your database in production// express.raw keeps the exact bytes the signature coversapp.post('/webhooks/tanqory', express.raw({ type: 'application/json' }), (req, res) => {  const ok = verifyTanqory(req.body, req.get('X-Tanqory-Signature'), process.env.TANQORY_WEBHOOK_SECRET)  if (!ok) return res.sendStatus(400)  const event = JSON.parse(req.body)  if (seen.has(event.idempotencyKey)) return res.sendStatus(200) // already handled  seen.add(event.idempotencyKey)  res.sendStatus(200) // answer first: 2xx within 30 seconds  setImmediate(() => handle(req.get('X-Tanqory-Topic'), event)) // then do the work})app.listen(3000)

Subscribe

Create a subscription

One subscription is one topic sent to one URL. Create it with REST or GraphQL, and keep the secret the answer returns: it signs every delivery to that URL.

  • One topic per subscriptionSend event as a slash topic such as orders/paid. For more topics on the same URL, create one subscription each.
  • Owners and adminsCreating, changing, testing and deleting need the store owner or an admin with the manage webhooks permission. Reading needs view access.
  • A secret per subscriptionThe server makes a 64 character hex secret for every subscription. GraphQL returns it once, on create.
  • No duplicatesThe same URL and topic twice in one store answers 409. Use https for your URL so the body is encrypted in transit.
  • Or ask the assistantThe Tanqory AI assistant can list, create, test and delete a store's webhooks with your permissions, and never shows the secret, so setup takes a sentence instead of a ticket.
POST /notifications/webhooks201 Created
# The Store API answers on api.tanqory.com for every storecurl -X POST https://api.tanqory.com/v1/stores/$STORE_ID/notifications/webhooks \  -H "Authorization: Bearer $TANQORY_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "event": "orders/paid",    "format": "JSON",    "url": "https://example.com/webhooks/tanqory",    "apiVersion": "2025-01"  }'

Topics

Topics

Every topic a store can subscribe to, grouped by resource, with the fields its data carries. The dedupe key tells you whether one key means one fact or one occurrence. A fact key repeats only for the same fact; an occurrence key adds the row of each occurrence, so two stock changes on one item get two keys.

79 topics for stores, 3 more for apps, in 23 groups

orders12

  • orders/createLegacy name order_creationKey: <event>:<id>8 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
  • orders/updatedLegacy name order_updatedKey: <event>:<id>:<row id>8 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
  • orders/paidLegacy name order_paymentKey: <event>:<id>8 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
  • orders/fulfilledLegacy name order_fulfilledKey: <event>:<id>13 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
    • fulfillmentIdstringoptional
    • trackingNumberstringoptional
    • trackingUrlstringoptional
    • trackingCompanystringoptional
    • shippedAtstringoptional
  • orders/partially_fulfilledKey: <event>:<id>:<row id>8 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
  • orders/cancelledLegacy name order_cancelledKey: <event>:<id>12 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
    • refundAmountnumberoptional
    • refundReasonstringoptional
    • cancelReasonstringoptional
    • cancelledAtstringoptional
  • orders/tags_addedKey: <event>:<id>:<row id>8 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
  • orders/tags_removedKey: <event>:<id>:<row id>8 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
  • orders/notes_addedKey: <event>:<id>:<row id>8 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
  • orders/risk_updatedLegacy name order_risk_assessment_changedKey: <event>:<id>:<row id>8 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
  • orders/editedKey: <event>:<id>:<row id>9 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobject
      • idstring
      • orderNumberstring | numberoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstring
    • orderEditIdstring
  • orders/deletedKey: <event>:<id>9 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobject
      • idstring
      • orderNumberstring | numberoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstring
    • deletedAtstring

refunds1

  • refunds/createLegacy name refund_creationKey: <event>:<id>:<row id>15 fields
    • storeIdstring
    • orderIdstring
    • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
      • totalPricestring | numberoptional
      • currencystringoptional
    • orderDataobjectoptional
      • currencystringoptional
      • totalAmountnumberoptional
      • subtotalAmountnumberoptional
      • taxAmountnumberoptional
      • shippingAmountnumberoptional
      • discountAmountnumberoptional
      • customerEmailstringoptional
      • customerFirstNamestringoptional
      • customerLastNamestringoptional
      • paymentMethodstringoptional
      • statusUrlstringoptional
      • lineItemsmap[]optional
      • shippingAddressFullmapoptional
      • billingAddressFullmapoptional
    • timestampstringoptional
    • refundAmountnumberoptional
    • refundReasonstringoptional
    • refundedSubtotalnumberoptional
    • refundedTaxnumberoptional
    • refundedShippingnumberoptional
    • refundedLineItemsmap[]optional
    • refundedAtstringoptional

draft_orders3

  • draft_orders/createKey: <event>:<id>7 fields
    • storeIdstring
    • draftOrderIdstring
    • draftOrderobject
      • idstring
      • namestring
      • statusstring
      • totalPricenumber
      • currencystring
      • customerIdstringoptional
    • timestampstring
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • invoiceUrlstringoptional
  • draft_orders/deletedKey: <event>:<id>4 fields
    • storeIdstring
    • draftOrderIdstring
    • draftOrderobject
      • idstring
      • namestring
      • statusstring
      • totalPricenumber
      • currencystring
      • customerIdstringoptional
    • timestampstring
  • draft_orders/updatedKey: <event>:<id>:<row id>4 fields
    • storeIdstring
    • draftOrderIdstring
    • draftOrderobject
      • idstring
      • namestring
      • statusstring
      • totalPricenumber
      • currencystring
      • customerIdstringoptional
    • timestampstring

fulfillments3

  • fulfillments/createKey: <event>:<id>11 fields
    • storeIdstring
    • fulfillmentIdstring
    • fulfillmentobject
      • idstring
    • orderIdstring
    • orderobject
      • idstring
      • orderNumberstring | number
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
    • trackingNumberstringoptional
    • trackingUrlstringoptional
    • trackingCompanystringoptional
    • timestampstring
  • fulfillments/tracking_addedKey: <event>:<id>:<row id>10 fields
    • storeIdstring
    • fulfillmentIdstring
    • orderIdstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • trackingNumberstringoptional
    • trackingUrlstringoptional
    • trackingCompanystringoptional
    • timestampstringoptional
  • fulfillments/deliveredKey: <event>:<id>11 fields
    • storeIdstring
    • fulfillmentIdstring
    • orderIdstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • trackingNumberstringoptional
    • trackingUrlstringoptional
    • trackingCompanystringoptional
    • deliveredAtstringoptional
    • timestampstringoptional

fulfillment_events1

  • fulfillment_events/createKey: <event>:<id>:<row id>13 fields
    • storeIdstring
    • fulfillmentIdstring
    • orderIdstringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • trackingNumberstringoptional
    • trackingUrlstringoptional
    • trackingCompanystringoptional
    • timestampstringoptional
    • statusstringoptional
    • estimatedDeliverystringoptional
    • happenedAtstringoptional

returns4

  • returns/requestedKey: <event>:<id>8 fields
    • storeIdstring
    • returnIdstring
    • orderIdstring
    • orderobject
      • idstring
      • orderNumberstring | numberoptional
      • statusUrlstringoptional
    • customerIdstringoptional
    • refundReasonstringoptional
    • customerobjectoptional
      • idstring
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • timestampstring
  • returns/approvedKey: <event>:<id>8 fields
    • storeIdstring
    • returnIdstring
    • orderIdstring
    • orderobject
      • idstring
      • orderNumberstring | numberoptional
      • statusUrlstringoptional
    • customerIdstringoptional
    • refundReasonstringoptional
    • customerobjectoptional
      • idstring
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • timestampstring
  • returns/declinedKey: <event>:<id>:<row id>9 fields
    • storeIdstring
    • returnIdstring
    • orderIdstring
    • orderobject
      • idstring
      • orderNumberstring | numberoptional
      • statusUrlstringoptional
    • customerIdstringoptional
    • refundReasonstringoptional
    • customerobjectoptional
      • idstring
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • timestampstring
    • declineReasonstringoptional
  • returns/closedKey: <event>:<id>:<row id>9 fields
    • storeIdstring
    • returnIdstring
    • orderIdstring
    • orderobject
      • idstring
      • orderNumberstring | numberoptional
      • statusUrlstringoptional
    • customerIdstringoptional
    • refundReasonstringoptional
    • customerobjectoptional
      • idstring
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • timestampstring
    • closedAtstringoptional

products4

  • products/createLegacy name product_creationKey: <event>:<id>2 fields
    • storeIdstring
    • productIdstring
  • products/updateLegacy name product_updateKey: <event>:<id>:<row id>2 fields
    • storeIdstring
    • productIdstring
  • products/deleteLegacy name product_deletionKey: <event>:<id>2 fields
    • storeIdstring
    • productIdstring
  • products/publishedKey: <event>:<id>:<row id>6 fields
    • storeIdstring
    • productIdstring
    • productobject
      • idstring
    • channelsstring[]optional
    • salesChannelstringoptional
    • timestampstring

collections3

  • collections/createKey: <event>:<id>4 fields
    • storeIdstring
    • collectionIdstring
    • collectionobject
      • idstring
      • titlestring
      • handlestring
    • timestampstring
  • collections/updateKey: <event>:<id>:<row id>4 fields
    • storeIdstring
    • collectionIdstring
    • collectionobject
      • idstring
      • titlestring
      • handlestring
    • timestampstring
  • collections/deleteKey: <event>:<id>4 fields
    • storeIdstring
    • collectionIdstring
    • collectionobject
      • idstring
      • titlestring
      • handlestring
    • timestampstring

inventory_levels4

  • inventory_levels/updateKey: <event>:<id>:<row id>2 fields
    • storeIdstring
    • productIdstring
  • inventory_levels/low_stockKey: <event>:<id>:<row id>2 fields
    • storeIdstring
    • productIdstring
  • inventory_levels/out_of_stockKey: <event>:<id>:<row id>2 fields
    • storeIdstring
    • productIdstring
  • inventory_levels/restockedKey: <event>:<id>:<row id>2 fields
    • storeIdstring
    • productIdstring

customers8

  • customers/createLegacy name customer_creationKey: <event>:<id>4 fields
    • storeIdstring
    • customerIdstring
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • timestampstringoptional
  • customers/updateLegacy name customer_updateKey: <event>:<id>:<row id>2 fields
    • storeIdstring
    • customerIdstring
  • customers/deleteKey: <event>:<id>2 fields
    • storeIdstring
    • customerIdstring
  • customers/enableKey: <event>:<id>2 fields
    • storeIdstring
    • customerIdstring
  • customers/taggedKey: <event>:<id>:<row id>8 fields
    • storeIdstring
    • customerIdstring
    • customerobject
      • idstring
      • emailstring
      • firstNamestringoptional
      • lastNamestringoptional
    • tagsstring[]
    • actionenumone of added, removed
    • addedTagsstring[]optional
    • removedTagsstring[]optional
    • timestampstring
  • customers/marketing_consent_updatedKey: <event>:<id>:<row id>2 fields
    • storeIdstring
    • customerIdstring
  • customers/redactApps onlyKey: <event>:<id>:<row id>5 fields
    • storeIdstring
    • customerIdstring
    • customerobject
      • idstring
    • ordersRedactedintegeroptional
    • timestampstring
  • customers/disableKey: <event>:<id>:<row id>2 fields
    • storeIdstring
    • customerIdstring

checkouts2

  • checkouts/createLegacy name checkout_creationKey: <event>:<id>2 fields
    • storeIdstring
    • checkoutIdstring
  • checkouts/updateKey: <event>:<id>:<row id>2 fields
    • storeIdstring
    • checkoutIdstring

carts4

  • carts/abandonedKey: <event>:<id>3 fields
    • storeIdstring
    • cartIdstring
    • checkoutIdstring
  • carts/recoveredKey: <event>:<id>3 fields
    • storeIdstring
    • cartIdstring
    • checkoutIdstring
  • carts/createKey: <event>:<id>5 fields
    • storeIdstring
    • cartIdstring
    • checkoutIdstring
    • cartobject
      • idstring
      • tokenstringoptional
    • timestampstring
  • carts/updateKey: <event>:<id>:<row id>5 fields
    • storeIdstring
    • cartIdstring
    • checkoutIdstring
    • lineItemsobjectoptional
      • countintegeroptional
      • subtotalnumberoptional
      • currencystringoptional
    • timestampstring

companies8

  • companies/createKey: <event>:<id>4 fields
    • storeIdstring
    • companyIdstring
    • companyobject
      • idstring
      • namestring
      • externalIdstring
    • timestampstring
  • companies/updateKey: <event>:<id>:<row id>5 fields
    • storeIdstring
    • companyIdstring
    • companyobject
      • idstring
      • namestring
      • externalIdstring
    • timestampstring
    • changedFieldsstring[]
  • companies/deleteKey: <event>:<id>4 fields
    • storeIdstring
    • companyIdstring
    • companyobject
      • idstring
      • namestring
      • externalIdstring
    • timestampstring
  • companies/location_createdKey: <event>:<id>:<row id>5 fields
    • storeIdstring
    • companyIdstring
    • companyLocationIdstring
    • locationobject
      • idstring
      • namestring
      • shippingAddressmap
      • isPrimaryboolean
    • timestampstring
  • companies/location_updatedKey: <event>:<id>:<row id>5 fields
    • storeIdstring
    • companyIdstring
    • companyLocationIdstring
    • locationobject
      • idstring
      • namestring
      • shippingAddressmap
      • isPrimaryboolean
    • timestampstring
  • companies/location_deletedKey: <event>:<id>:<row id>5 fields
    • storeIdstring
    • companyIdstring
    • companyLocationIdstring
    • locationobject
      • idstring
      • namestring
      • shippingAddressmap
    • timestampstring
  • companies/contact_addedKey: <event>:<id>:<row id>8 fields
    • storeIdstring
    • companyIdstring
    • companyLocationIdstring
    • companyContactIdstring
    • customerIdstring
    • contactobject
      • idstring
      • customerIdstring
      • emailstringoptional
      • rolestring
    • timestampstring
    • grantedboolean
  • companies/contact_removedKey: <event>:<id>:<row id>7 fields
    • storeIdstring
    • companyIdstring
    • companyLocationIdstring
    • companyContactIdstring
    • customerIdstring
    • contactobject
      • idstring
      • customerIdstring
      • emailstringoptional
      • rolestring
    • timestampstring

app1

  • app/uninstalledLegacy name app_uninstalledApps onlyKey: <event>:<id>3 fields
    • storeIdstring
    • installationIdstring
    • occurredAtdatetime

bulk_jobs4

  • bulk_jobs/createKey: <event>:<id>8 fields
    • storeIdstring
    • jobIdstring
    • entitystring
    • operationenumone of EXPORT, IMPORT
    • modestringoptional
    • createdBystringoptional
    • actorTypeenumoptionalone of USER, APP, SYSTEM
    • createdAtstringoptional
  • bulk_jobs/completeKey: <event>:<id>11 fields
    • storeIdstring
    • jobIdstring
    • entitystring
    • operationenumone of EXPORT, IMPORT
    • modestringoptional
    • statusliteralone of COMPLETED
    • outcomeenumone of OK, PARTIAL
    • countsobject
      • totalinteger
      • processedinteger
      • createdinteger
      • updatedinteger
      • deletedinteger
      • skippedinteger
      • conflictedintegeroptional
      • failedinteger
      • unprocessedinteger
      • deletedByReplaceinteger
    • issuesSummaryobject[]
      • codestring
      • severityenumone of ERROR, WARNING, INFO
      • countinteger
    • startedAtstringoptional
    • completedAtstringoptional
  • bulk_jobs/failedKey: <event>:<id>11 fields
    • storeIdstring
    • jobIdstring
    • entitystring
    • operationenumone of EXPORT, IMPORT
    • modestringoptional
    • statusliteralone of FAILED
    • outcomeliteralone of ERROR
    • errorCodestring
    • errorMessagestring
    • countsobject
      • totalinteger
      • processedinteger
      • createdinteger
      • updatedinteger
      • deletedinteger
      • skippedinteger
      • conflictedintegeroptional
      • failedinteger
      • unprocessedinteger
      • deletedByReplaceinteger
    • completedAtstringoptional
  • bulk_jobs/cancelledKey: <event>:<id>8 fields
    • storeIdstring
    • jobIdstring
    • entitystring
    • operationenumone of EXPORT, IMPORT
    • modestringoptional
    • statusliteralone of CANCELLED
    • countsobject
      • totalinteger
      • processedinteger
      • createdinteger
      • updatedinteger
      • deletedinteger
      • skippedinteger
      • conflictedintegeroptional
      • failedinteger
      • unprocessedinteger
      • deletedByReplaceinteger
    • cancelledAtstringoptional

marketing3

  • marketing/workflow_failedKey: <event>:<id>:<row id>7 fields
    • storeIdstring
    • workflowIdstring
    • workflowobject
      • idstring
      • namestringoptional
    • errorobject
      • messagestring
    • sourceTriggerstringoptional
    • executionIdstring
    • failedAtstring
  • marketing/campaign_sentKey: <event>:<id>5 fields
    • storeIdstring
    • campaignIdstring
    • campaignobject
      • idstring
      • namestringoptional
      • subjectstringoptional
    • recipientCountintegeroptional
    • timestampstring
  • marketing/campaign_completedKey: <event>:<id>:<row id>6 fields
    • storeIdstring
    • campaignIdstring
    • campaignobject
      • idstring
    • sentCountintegeroptional
    • failedCountintegeroptional
    • timestampstring

store1

  • store/redactApps onlyKey: <event>:<id>4 fields
    • storeIdstring
    • appHandlestring
    • uninstalledAtstring
    • timestampstring

transactions1

  • transactions/createKey: <event>:<id>5 fields
    • storeIdstring
    • orderIdstring
    • transactionIdstring
    • transactionobject
      • idstring
      • kindstring
      • statusstring
      • amountnumber
      • currencystring
      • gatewaystringoptional
      • paymentMethodstringoptional
    • timestampstring

locations3

  • locations/createKey: <event>:<id>4 fields
    • storeIdstring
    • locationIdstring
    • locationobject
      • idstring
      • namestring
      • isDefaultbooleanoptional
      • isActivebooleanoptional
    • timestampstring
  • locations/updateKey: <event>:<id>:<row id>4 fields
    • storeIdstring
    • locationIdstring
    • locationobject
      • idstring
      • namestring
      • isDefaultbooleanoptional
      • isActivebooleanoptional
    • timestampstring
  • locations/deleteKey: <event>:<id>4 fields
    • storeIdstring
    • locationIdstring
    • locationobject
      • idstring
      • namestring
      • isDefaultbooleanoptional
      • isActivebooleanoptional
    • timestampstring

segments3

  • segments/createKey: <event>:<id>4 fields
    • storeIdstring
    • segmentIdstring
    • segmentobject
      • idstring
      • namestring
      • customerCountintegeroptional
    • timestampstring
  • segments/updateKey: <event>:<id>:<row id>4 fields
    • storeIdstring
    • segmentIdstring
    • segmentobject
      • idstring
      • namestring
      • customerCountintegeroptional
    • timestampstring
  • segments/deleteKey: <event>:<id>4 fields
    • storeIdstring
    • segmentIdstring
    • segmentobject
      • idstring
      • namestring
      • customerCountintegeroptional
    • timestampstring

discounts3

  • discounts/createKey: <event>:<id>4 fields
    • storeIdstring
    • discountIdstring
    • discountobject
      • idstring
      • codestringoptional
      • titlestringoptional
      • typestringoptional
      • statusstringoptional
    • timestampstring
  • discounts/updateKey: <event>:<id>:<row id>4 fields
    • storeIdstring
    • discountIdstring
    • discountobject
      • idstring
      • codestringoptional
      • titlestringoptional
      • typestringoptional
      • statusstringoptional
    • timestampstring
  • discounts/deleteKey: <event>:<id>4 fields
    • storeIdstring
    • discountIdstring
    • discountobject
      • idstring
      • codestringoptional
      • titlestringoptional
      • typestringoptional
      • statusstringoptional
    • timestampstring

gift_cards3

  • gift_cards/createKey: <event>:<id>8 fields
    • storeIdstring
    • giftCardIdstring
    • giftCardobject
      • idstring
      • maskedCodestringoptional
      • currencystringoptional
      • initialValuenumberoptional
      • currentBalancenumberoptional
    • timestampstring
    • customerIdstringoptional
    • customerobjectoptional
      • idstringoptional
      • emailstringoptional
      • firstNamestringoptional
      • lastNamestringoptional
    • recipientEmailstringoptional
    • messagestringoptional
  • gift_cards/sentKey: <event>:<id>:<row id>7 fields
    • storeIdstring
    • giftCardIdstring
    • giftCardobject
      • idstring
      • maskedCodestringoptional
      • currencystringoptional
      • initialValuenumberoptional
      • currentBalancenumberoptional
    • timestampstring
    • recipientEmailstringoptional
    • purchaserobjectoptional
      • customerIdstringoptional
      • emailstringoptional
      • namestringoptional
    • orderobjectoptional
      • idstringoptional
      • orderNumberstring | numberoptional
  • gift_cards/usedKey: <event>:<id>:<row id>7 fields
    • storeIdstring
    • giftCardIdstring
    • giftCardobject
      • idstring
      • maskedCodestringoptional
      • currencystringoptional
      • initialValuenumberoptional
      • currentBalancenumberoptional
    • timestampstring
    • amountRedeemednumberoptional
    • newBalancenumberoptional
    • orderIdstringoptional

subscriptions3

  • subscriptions/createKey: <event>:<id>4 fields
    • storeIdstring
    • subscriptionContractIdstring
    • subscriptionobject
      • idstring
      • customerIdstringoptional
      • statusstringoptional
      • nextBillingDatestringoptional
    • timestampstring
  • subscriptions/cancelledKey: <event>:<id>:<row id>5 fields
    • storeIdstring
    • subscriptionContractIdstring
    • subscriptionobject
      • idstring
      • customerIdstringoptional
      • statusstringoptional
      • nextBillingDatestringoptional
    • timestampstring
    • cancelReasonstringoptional
  • subscriptions/billing_failedKey: <event>:<id>:<row id>7 fields
    • storeIdstring
    • subscriptionContractIdstring
    • subscriptionobject
      • idstring
    • errorMessagestringoptional
    • willRetrybooleanoptional
    • retryCountintegeroptional
    • timestampstring

Delivery

What we send

Every delivery is a POST with the same headers and the same envelope. The topic travels in X-Tanqory-Topic, not in the body, and the body is always JSON.

  • id and idempotencyKeyid is the event and stays the same on every retry of it; idempotencyKey is the fact to dedupe on.
  • apiVersionDeliveries carry 2025-01 in the body.
  • dataThe event's fields as listed under its topic. When an order event carries orderData, data is that object.
Request headersExact names, on every delivery
The headers on every Tanqory webhook delivery and what each carries
HeaderValue
Content-Typeapplication/json
X-Tanqory-Webhook-IdThe id of the subscription that sent it
X-Tanqory-Signaturet=<unix seconds>,v1=<hex>, a second v1 during a rotation
X-Tanqory-EventThe legacy name when the topic has one, otherwise the topic
X-Tanqory-TopicThe slash topic, such as orders/paid
User-AgentTanqory-Webhook/1.0
Example: body
{  "id": "evt-example-1",  "event": "order_payment",  "apiVersion": "2025-01",  "createdAt": "2026-10-09T09:20:00.000Z",  "storeId": "store-example-01",  "idempotencyKey": "order.paid:ord-example-1042",  "data": {    "storeId": "store-example-01",    "orderId": "ord-example-1042",    "orderNumber": 1042,    "customerId": "cus-example-77",    "timestamp": "2026-10-09T09:20:00.000Z"  }}

Signing

Verify the signature

Check the signature on the raw bytes before you parse the body. Three rules cover it.

verify.js
const crypto = require('node:crypto')// rawBody: the exact bytes received (Buffer). Do not re-serialize JSON.function verifyTanqory(rawBody, header, secret, toleranceSec = 300) {  if (!header || !secret) return false  let t = null  const sigs = []  for (const part of header.split(',')) {    const i = part.indexOf('=')    if (i < 0) continue    const k = part.slice(0, i).trim()    const v = part.slice(i + 1).trim()    if (k === 't') t = v    else if (k === 'v1' && v) sigs.push(v)  }  if (!t || !/^\d+$/.test(t) || sigs.length === 0) return false  if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > toleranceSec) return false  const expected = crypto    .createHmac('sha256', secret) // secret string as-is, NOT hex-decoded    .update(`${t}.`)    .update(rawBody)    .digest()  // Check EVERY v1 entry: two are sent during a rotation grace window.  return sigs.some((s) => {    if (!/^[0-9a-f]{64}$/.test(s)) return false    return crypto.timingSafeEqual(expected, Buffer.from(s, 'hex'))  })}module.exports = { verifyTanqory }
Signature labComputed in your browser, nothing is sent.
X-Tanqory-Signature we would sendX-Tanqory-Signature: t=1791561600,v1=1ca6cc7691673eabb05fd1a4dd844af79937c8540138c0981cde3c68c3537ae6

Valid: a v1 matches

Change one character of the body or the secret and the received header stops matching.

Retries

Failures and retries

A failed delivery is tried again on its own. Keep your endpoint answering, because a long run of failures turns the subscription off.

  • What failsA status outside 200 to 299, a network error or no answer within 30 seconds.
  • Retried with backoffFailed deliveries are retried automatically with a growing wait, and each retry is signed again with a fresh t.
  • Off after 20 in a row20 failed attempts in a row turn the subscription off. A success resets the count; turning it back on does not, so fix the endpoint first.
  • Every attempt loggedEach attempt is a row you can read: status, HTTP code, error, attempt number, duration and size.
Example: GET .../attempts
GET /api/v1/stores/store-example-01/notifications/webhooks/wh-example-7f3a/attempts?page=1&limit=20{  "id": "att-example-903",  "webhookId": "wh-example-7f3a",  "event": "order_payment",  "topic": "orders/paid",  "status": "failed",  "httpStatusCode": 503,  "errorMessage": "Example: HTTP 503",  "attemptNumber": 2,  "duration": 30000,  "payloadSize": 412,  "attemptedAt": "2026-10-09T09:22:00.000Z"}

Test and rotate

Test and rotate

Send a test delivery before real events arrive, and rotate a secret without dropping one.

  • Test sendSent at once with the same headers and signature, no retries, and it works while the subscription is off. The answer tells you the status and the time it took.
  • Rotate a secretThe new secret signs at once; the old one keeps signing beside it for 24 hours, so the header carries two v1, new first.
  • Rotate once per windowRotating again inside the 24 hours ends the oldest secret at once. Verify every v1, not only the last, or a rotation breaks you.
Example: test delivery body
{  "id": "6f1c2a9e-example-uuid",  "event": "order_payment",  "apiVersion": "2025-01",  "createdAt": "2026-10-09T09:30:00.000Z",  "storeId": "store-example-01",  "idempotencyKey": "b84d07c1-example-uuid",  "data": {    "test": true,    "message": "Test payload sent successfully",    "event": "order_payment",    "timestamp": "2026-10-09T09:30:00.000Z"  }}
Example: header during a rotation
X-Tanqory-Signature: t=1791561600,  v1=<hmac with the new secret>,  v1=<hmac with the old secret, for 24 hours>

Reference

Endpoints

The REST operations under /api/v1/stores/{storeId}/notifications/webhooks. Limits are per minute for each client.

Webhook operationsStore API v1
Store API webhook operations, who may call each and its rate limit
OperationWhoLimit
POST/Owner or admin10
GET/View accessStore API default
GET/{webhookId}View accessStore API default
PATCH/{webhookId}Owner or admin30
DELETE/{webhookId}Owner or admin10
POST/{webhookId}/testOwner or admin5
GET/{webhookId}/attemptsView accessStore API default
POST/{webhookId}/rotate-secretOwner or admin5
GET/signing-secretManage webhooks permissionStore API default

The GraphQL Admin schema has the same: webhookSubscription and webhookSubscriptions to read, webhookSubscriptionCreate, Update, Delete, Resume and RotateSecret to write, and webhookDeliveries with webhookDeliveryReplay to read and resend deliveries.

The store-wide POST /rotate-secret is deprecated: it gives every subscription the same new secret. Rotate per subscription instead.

Wire your store to your systems

Subscribe, verify and let orders, stock and returns reach your tools the moment they happen. AI is part of every plan.

  • 79 topics for stores
  • HMAC-SHA256 on every delivery
  • Test sends while off