Inbound webhooks and callbacks: stores, Store Syria and partners
Reference for the requests the platform receives: Salla, Zid, Shopify and WooCommerce store events, the legacy Store Syria API, and carrier and payment callbacks, with verification and effects.
Last updated 22 October 2026 · Last verified 22 October 2026
Purpose#
This page lists the requests the platform receives from other systems: store events from WooCommerce, Shopify, Salla and Zid, the legacy Store Syria "security id" API, and status callbacks from shipping carriers and payment gateways. For each one it gives the address, how the request is verified and what it changes. It is a reference for developers who build or debug a connector. Notifications that the platform sends to your own system are covered in the outgoing webhooks articles.
Who needs it and what you need#
- Developers of store plugins, partner systems and test tools. Most merchants never call these addresses: connecting a store from Store integrations in the merchant panel sets everything up (WooCommerce and Shopify register their own webhooks; Salla and Zid work through the platform's apps).
- Every address is under
/apion the platform's domain, takes JSON and needs no browser session. - Secrets (tokens, keys, signing secrets) are never printed here. The examples use placeholders such as
YOUR_WEBHOOK_TOKENandYOUR_SECRET; keep real values on your server.
At a glance#
| Source | Request | How it is verified | What it changes |
|---|---|---|---|
| WooCommerce | POST /api/stores/woocommerce/{token} |
token in the path plus X-WC-Webhook-Signature |
saves the order, then creates a parcel when the connection is active and automatic parcel creation is on |
| Shopify custom app | POST /api/stores/shopify/{token} |
token in the path plus X-Shopify-Hmac-Sha256 |
same as WooCommerce |
| Shopify public app | POST /api/stores/shopify/app |
X-Shopify-Hmac-Sha256 with the app secret |
same; the topic app/uninstalled pauses the connection |
| Shopify privacy | POST /api/stores/shopify/compliance |
X-Shopify-Hmac-Sha256 with the app secret |
erases saved copies of customer data |
| Shopify checkout rates | POST /api/stores/shopify/rates |
Shopify checkout callback that asks for a delivery price | returns a shipping rate, changes nothing |
| Salla app events | POST /api/salla/events |
X-Salla-Signature, or Authorization equal to the secret |
logs the event; authorize and uninstall update the link; order events create parcels |
| Salla onboarding | POST /api/salla/link |
header X-DS-Link-Secret |
links a Salla store to a merchant with a link code |
| Zid events | POST /api/zid/events |
X-Hook-Secret, or HTTP Basic credentials |
logs the event; orders become parcels |
| Store Syria (legacy) | /api/parcel/...-with-security, /api/merchant/verify, /api/secret/cheack |
the key, as a Bearer token with X-Platform or in the JSON body (see below) |
creates and reads the key owner's parcels |
| LFL Express | POST /api/webhooks/lfl |
X-Signature |
updates the parcel's external status |
| Other carriers | POST /api/carriers/webhook/{token} |
the check the platform operator has set for the carrier (see below) | updates the external status |
| ShamCash | POST /api/payments/shamcash/webhook |
encrypted body | confirms or fails a payment |
| Paymera | GET or POST /api/payments/paymera/callback?ref=... |
the status is re-read from Paymera | confirms or fails a payment |
Rules that apply to all of them#
- No CSRF and no session. These routes sit in the API group; the sender needs only the address and the verification listed above.
- Quick answer, work afterwards. Store endpoints answer at once (
{"ok":true}, or{"message":"ok"}for Salla) and build the parcel after the response (when the connection is active and automatic parcel creation is on). Scheduled jobs retry anything left pending: store orders and Salla orders every 5 minutes, Zid events every minute. - Status codes the sender sees.
200accepted (also for events that are ignored),401failed verification,404unknown token,400unreadable payload,500processing error, which tells the sender to retry. - Request ceiling. Every
/apiroute first passes the general limiter of 60 requests per minute per IP address. The higher route limits written in the code (240, 600 and so on) never raise that ceiling, so a sender that delivers more than 60 events a minute from one IP address gets429. See the rate limits article. - Repeated deliveries. A store order is identified by the connection and the store's order id, so the same event delivered twice updates the saved order and does not create a second parcel. The parcel's
invoice_nois the first four letters of the platform in capitals, the connection id, a hyphen and the store's order number (for exampleWOOC12-1001); Zid parcels useZID-and the Zid order id. - Verify against the raw body. Signatures are computed on the exact bytes received. Do not re-encode the JSON before checking or forwarding it.
Store platforms#
WooCommerce#
When the merchant connects the store, the platform creates the webhook in the store (topics order.created and order.updated) with a random secret kept for that connection. The delivery address is /api/stores/woocommerce/ plus a 40-character token. The request must carry X-WC-Webhook-Signature: the base64 of the HMAC-SHA256 of the body with that secret. A wrong or missing signature gets 401 with {"message":"Invalid signature"}; an unknown token gets 404 with {"message":"Unknown store"}.
Order states: cancelled, refunded, failed and trashed orders are marked cancelled; completed orders are skipped (already shipped); cash on delivery orders (payment_method equal to cod) and paid orders become parcels, and the amount to collect is the order total only for cod; other unpaid orders wait for a later update.
Shopify#
A custom app uses /api/stores/shopify/{token}. The connection registers the topics orders/create and orders/updated, and each request is signed in X-Shopify-Hmac-Sha256 (base64 HMAC-SHA256 of the body) with the app secret the merchant entered. The public app uses one address, /api/stores/shopify/app, signed with the platform app's secret. The store comes from X-Shopify-Shop-Domain; an unknown store is ignored with {"ok":true}, and the topic app/uninstalled pauses the connection. The mandatory privacy topics go to /api/stores/shopify/compliance: customers/redact clears the saved payload and customer name of the orders listed in the request, and shop/redact does so for all the store's orders, pauses the connection and removes its credentials (parcels stay as shipping records); other topics are only logged. Checkout rates (/api/stores/shopify/rates) answer with a rate named Delivery Syria only for destination country SY, a recognised governorate and an active connection with a shipping company.
Order states: cancelled, voided and refunded orders are cancelled; fulfilled or partly fulfilled orders are skipped; paid, authorized, partly paid and cash on delivery orders become parcels; the rest wait for payment.
Salla#
POST /api/salla/events is the single address of the platform's Salla app. The check is X-Salla-Signature (hex HMAC-SHA256 of the raw body) or, when that header is absent, an Authorization header equal to the secret (with or without Bearer ). If the server has no secret configured, every request is refused with 401 and {"message":"unauthorized"}. A body that is not JSON gets 400 with {"message":"invalid payload"}; success is 200 with {"message":"ok"}; a processing error is 500, and an order event then stays unprocessed until the scheduled job retries it.
Every event is logged with its tokens removed. app.store.authorize saves the store's tokens (encrypted) and refreshes the store connection; app.uninstalled clears the tokens and pauses the connection; order.created, order.updated, order.status.updated, order.payment.updated and order.cancelled feed the order flow; other events are only logged. A parcel is created when the Salla status is under_review or in_progress and the order is cash on delivery or paid; payment_pending and draft orders wait, cancelled orders are cancelled and any other status is skipped. By default only orders whose shipping app is the platform's own are taken; the platform operator can turn this restriction off.
POST /api/salla/link is called by Salla's app function during onboarding, not by the merchant. It needs the header X-DS-Link-Secret and a JSON body with merchant_id (digits only) and code, the link code the merchant gets from the platform (valid for 10 minutes). 401 means a wrong secret; 422 means the link was refused: an invalid store number, an invalid or expired code, or a merchant who cannot link (the account is not an active verified merchant, there is no active pickup location, the plan does not include store connections, or the plan's store limit is reached), and the response message says which; 200 is a successful link; an unexpected error gives 500.
Zid#
POST /api/zid/events (one extra path segment is accepted) must carry the configured Zid secret: the X-Hook-Secret header must equal the secret, or the HTTP Basic user name and password must equal the configured pair. Failure returns 401 with {"ok":false}. When the merchant connects the store, the platform subscribes it to order.create, order.status.update and order.payment_status.update.
The endpoint stores the event and answers {"ok":true}. A command that runs right after the response, and every minute, turns order events into parcels: the order must be cash on delivery or paid, the store must be linked to a merchant who has chosen a shipping company and a pickup location, and, by default, the shipping method name must contain "delivery syria" (the platform operator can turn this restriction off). An unpaid online order waits for the payment event.
Store Syria and key-based platforms (legacy)#
This is the older "security id" API used by the Store Syria plugin, the old WooCommerce plugin and custom platforms. It is kept for those clients; use the Merchant API for new integrations.
- Key. A platform merchant creates it in Webhook and API, under Webhook keys (Store Syria, WooCommerce plugin, Salla, API) or Custom platforms (up to 5, each with its own
X-Platformvaluecustom:<name>). The key starts withsk_. When plans apply, generating or renewing a Store Syria, WooCommerce plugin, Salla or API key needs thestoresplan feature (apifor the API key). - Where the key goes.
POST /api/parcel/create-with-security,GET /api/parcel/list-with-securityandGET /api/parcel/tracking-logs/{tracking}takeAuthorization: Bearer YOUR_KEYandX-Platformequal to the key's type; theapiKeyheader of the mobile API is not used. Missing headers give401; an unknown key gives403withInvalid credentials.POST /api/merchant/verifyandPOST /api/secret/cheacktake the key in the JSON body instead, as the table shows.
| Request | Purpose |
|---|---|
POST /api/merchant/verify |
body security_id (10 to 255 characters) and platform (woocommerce, salla or storesyria); returns merchant id, business name, company id and balances; the account must belong to the platform company; invalid input gives 422 and a wrong key or platform 403 |
POST /api/parcel/create-with-security |
creates a parcel for the key owner: hub_id, or Governorate or delivery_city to find the hub, plus customer_name, customer_phone, customer_address, cod_amount (or cod_amount2) and an optional invoice_no; a repeated invoice_no gives 409; a low balance returns 200 with wallet_status set to pending_payment |
GET /api/parcel/list-with-security |
the owner's parcels that were created through this API with the same X-Platform value (matched by invoice_no), with wallet balances; per_page 1 to 100 (default 10) and visible 0 or 1 |
GET /api/parcel/tracking-logs/{tracking} |
status and sync state of one of the owner's parcels |
POST /api/secret/cheack |
body secret; lists the 14 governorates (id, governorate, base_cost, available, code), where base_cost is the delivery charge for 1 kg for the merchant's company and available is false when it cannot be calculated; 400 without secret, 404 for an unknown secret (the route is spelled this way) |
Governorate helper requests such as GET /api/parcel/governorates are described in the public helpers article. Status updates that the platform sends back to Store Syria are not covered here.
Carriers and payment gateways#
- LFL Express,
POST /api/webhooks/lfl: headerX-Signature(hex HMAC-SHA256 of the body, with or withoutsha256=) made with the secret saved for the carrier, and the event name inX-Eventor in aneventfield of the body; eventsshipment.status_updated,shipments.createdandshipments.failed. No saved secret gives500webhook not configured, a bad signature401invalid signature, bad JSON400. A repeated delivery (samedelivery_id, or, when none is sent, an identical body) answersduplicate, already received. A status update stores the carrier's status on the parcel and moves the platform status through the carrier's status map; an event older than the stored status is ignored, and a parcel already Delivered or in Return to Courier keeps its platform status. - Other carriers,
POST /api/carriers/webhook/{token}(and/{account}for a merchant's own carrier account): the token identifies the carrier. The platform operator chooses how each carrier's callbacks are authenticated; a merchant's own account always requires an HMAC signature made with the secret the merchant saved. An unknown token or account gives404; a failed verification gives401withinvalid signature, or withwebhook secret not configuredwhen no secret is saved; a repeated event answersduplicate, and an unknown parcelparcel not foundwith200. - ShamCash,
POST /api/payments/shamcash/webhook(or/{gateway}): the JSON body is{"encData": "..."}, an encrypted message that the platform decrypts with the gateway's secret key. A message that cannot be decrypted or has expired gets400, an unknown gateway404. Status 4 confirms the payment, 3 marks it expired and 2 or 5 mark it failed; an unknown reference is answered200. - Paymera,
/api/payments/paymera/callback?ref=...: the platform ignores the content of the request and asks Paymera for the real payment status, so the status always comes from Paymera itself.GETredirects the customer back;POSTanswers{"ok":true}. A scheduled job (every 10 minutes) also asks the gateway about payments still pending after 12 minutes.
Common mistakes#
- Using a merchant API key on a store webhook. Store addresses use their own token and signature; only the Store Syria requests accept the
sk_platform keys. - Re-encoding the body before checking the signature. Compute the HMAC on the raw bytes.
- A store order does not become a parcel. Check that the store connection is active (it is paused after an uninstall or when access is lost) and that Automatically create a parcel for each order is turned on for the store, that a shipping company and a pickup location are chosen for the store (the order is marked failed with a note starting with «إعداد المتجر ناقص:»), that the order is paid or cash on delivery and not already fulfilled, and, for Salla and Zid, that the customer chose the platform's shipping.
- Sending many events at once returns
429. One IP address may send 60 requests a minute to/api. - A manual WooCommerce webhook without the connection's secret. The platform cannot verify it and answers
401.
Limits and permissions#
- The Store integrations and Webhook and API pages are for platform merchants (a merchant created by a delivery company is redirected away), and, when merchant plans are in force, connecting WooCommerce, Shopify or Salla needs a plan that includes store connections.
- Salla and Zid need their app credentials and secrets set on the server; without them verification fails.
- Zid uses a shared secret: keep it private, like a password, and change it if it leaks.
- Note: the Store Syria API is legacy; use the Merchant API for new integrations.
API
POST /api/stores/{platform}/{token}POST /api/stores/shopify/appPOST /api/stores/shopify/compliancePOST /api/stores/shopify/ratesPOST /api/salla/eventsPOST /api/salla/linkPOST /api/zid/eventsPOST /api/merchant/verifyPOST /api/parcel/create-with-securityGET /api/parcel/list-with-securityGET /api/parcel/tracking-logs/{track_id}POST /api/secret/cheackPOST /api/webhooks/lflPOST /api/carriers/webhook/{token}POST /api/payments/shamcash/webhookGET /api/payments/paymera/callback