Skip to main content

Webhooks

Where Mekari POS sends event notifications for your application. Each application manages its own endpoints, one per key (orders, products, collections, inventory); another application of the same merchant never sees them. These calls change your own settings, so they need no X-User-ID and no Idempotency-Key.

Event body​

Every event is one JSON object with the same keys (schema WebhookEvent): id, type, schema_version, occurred_at, company_id, outlet_id and data.

  • id identifies the change. It is the same on every retry and for every endpoint that receives the change. It is also sent as X-Mekari-Event-ID.
  • company_id is the merchant's company id, the one you know from Mekari SSO.
  • outlet_id is the outlet of the change, or null for company-wide events.
  • occurred_at is business time in UTC (Z), whole seconds.
  • schema_version is 1. New fields may be added to data without a new version, so ignore fields you do not know.

Thin data​

With payload set to thin (the default), data only names the resource. Call the matching GET to read it.

Eventsdata
order.paid, order.payment_updated, order.returned, order.refunded, order.exchangedWebhookOrderData
order.accepted, order.rejected, order.preparing, order.ready, order.served, order.completed, order.cancelledWebhookKitchenData
product.created, product.updated, product.deletedWebhookProductData
sku.changedWebhookSkuData
collection.created, collection.updated, collection.deleted, collection.reorderedWebhookCollectionData
inventory.availability_changedWebhookAvailabilityData

Notes on the order and kitchen events:

  • Order record events carry the order fields; order.returned, order.refunded and order.exchanged add return_id, refund_id or exchange_id. Their order_id is the original order, and the points are the ones stored on it when the event was written.
  • order.paid means the sale is completed at the till. For a Terms sale (pay later) check payment_status: it can still be unpaid. order.payment_updated is sent on every Terms payment, partial or full. Both carry payment_status and amount_owed.
  • Kitchen events carry kitchen_order_id, kitchen_status, order_id, sales_type and updated_at. kitchen_status is the event name after order.: order.ready has kitchen_status ready. order_id is null until a POS order exists for the ticket. A set order_id does not mean the order is paid: order.paid says the sale is completed at the till; for a Terms sale check payment_status.
  • On sku.changed, product_id is null when the variant no longer exists.
  • inventory.availability_changed never carries a quantity.

Full data​

With payload set to full (not for the inventory endpoint), data is the same object that the matching GET returns in its own data: GET /orders/{id}, GET /products/{id} or GET /collections/{id}. It is read when the event is sent, so it shows the resource as it is then, not as it was at the time of the change. For order.returned, order.refunded and order.exchanged, the order also has return_id, refund_id or exchange_id, so you still know which one happened.

  • Image URLs are empty strings, never null. Call GET to read images.
  • A product carries no stock, because stock is per outlet. Read it with GET /inventory/stock/{item_type}/{item_id}.
  • An order carries the same customer (member) data as GET /orders/{id}.
  • These are always thin, even on a full endpoint: *.deleted events, any event whose resource no longer exists, sku.changed, inventory.availability_changed, and a kitchen event while its order_id is still null. Once order_id is set, a kitchen event on a full endpoint carries the full order.
  • Rarely a full endpoint receives a thin body, when the resource could not be read at that moment. Check which fields are present, and call GET when you need the rest.

Headers and signature​

HeaderValue
X-Mekari-Signaturev1= and the hex HMAC-SHA256 of the signed string
X-Mekari-TimestampUnix time in seconds, when this attempt was signed
X-Mekari-Key-VersionThe version of the secret that signed it
X-Mekari-Delivery-IDIdentifies the delivery of this event to this endpoint
X-Mekari-Event-IDThe event id
User-Agentmekaripos-webhooks/1.0

The signed string is, in this order, joined with a dot: timestamp "." delivery_id "." event_id "." key_version "." raw_body. The key is the signing secret of that key version. Use the raw request body bytes, before any JSON parsing.

  1. Read the headers and the raw body.
  2. Reject the request when the timestamp is older than 5 minutes.
  3. Pick the secret that matches X-Mekari-Key-Version, build the string, compute the HMAC-SHA256 and compare it with the v1 value in constant time.

After rotate-secret (or a PUT that changes the url), the new version signs every request from that moment, retries included. Keep the previous secret for 24 hours only to verify requests that were already on their way, then delete it. Deploy the new secret quickly: until then your receiver cannot verify new requests.

Delivery, retries and pause​

Answer with any 2xx status within 10 seconds. Mekari POS never follows redirects: a 3xx answer is a final failure, and the Location is not requested.

  • A transport error, a timeout, 408, 429 and any 5xx are retried. Any other 4xx, and any 3xx, fail at once and are not retried.
  • There are at most 8 attempts: the first at once, then after about 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour, 3 hours and 6 hours (about 10 hours 42 minutes in all). A Retry-After header with a number of seconds is honoured when it is longer than that delay, up to 1 hour.
  • A delivery that fails for good counts as one failure. After 10 failures in a row, with no success between, the endpoint is paused and sends nothing. A PUT on the endpoint makes it active again; deliveries still waiting from before the pause are cancelled, not sent. Any 2xx resets the count.
  • Retries are made about once a minute, so the real delay can be up to a minute longer than the times above.

What to expect​

  • Delivery is at least once. The same event can arrive more than once: dedupe on id.
  • There is no ordering. Order events by occurred_at, and kitchen events by their kitchen_status.
  • Events are not replayed. After an outage on your side, or after the endpoint was paused, reconcile with GET /orders, GET /products and GET /collections.
  • You do not receive the change that your own application made through this API. Other applications of the same merchant do. This does not apply to inventory.availability_changed: it carries no origin, so you may receive an availability change caused by your own stock write.
  • Nothing is sent when the merchant's Public API add-on is off or expired.