Reference Developers

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 /api on 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_TOKEN and YOUR_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. 200 accepted (also for events that are ignored), 401 failed verification, 404 unknown token, 400 unreadable payload, 500 processing error, which tells the sender to retry.
  • Request ceiling. Every /api route 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 gets 429. 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_no is the first four letters of the platform in capitals, the connection id, a hyphen and the store's order number (for example WOOC12-1001); Zid parcels use ZID- 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-Platform value custom:<name>). The key starts with sk_. When plans apply, generating or renewing a Store Syria, WooCommerce plugin, Salla or API key needs the stores plan feature (api for the API key).
  • Where the key goes. POST /api/parcel/create-with-security, GET /api/parcel/list-with-security and GET /api/parcel/tracking-logs/{tracking} take Authorization: Bearer YOUR_KEY and X-Platform equal to the key's type; the apiKey header of the mobile API is not used. Missing headers give 401; an unknown key gives 403 with Invalid credentials. POST /api/merchant/verify and POST /api/secret/cheack take 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: header X-Signature (hex HMAC-SHA256 of the body, with or without sha256=) made with the secret saved for the carrier, and the event name in X-Event or in an event field of the body; events shipment.status_updated, shipments.created and shipments.failed. No saved secret gives 500 webhook not configured, a bad signature 401 invalid signature, bad JSON 400. A repeated delivery (same delivery_id, or, when none is sent, an identical body) answers duplicate, 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 gives 404; a failed verification gives 401 with invalid signature, or with webhook secret not configured when no secret is saved; a repeated event answers duplicate, and an unknown parcel parcel not found with 200.
  • 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 gets 400, an unknown gateway 404. Status 4 confirms the payment, 3 marks it expired and 2 or 5 mark it failed; an unknown reference is answered 200.
  • 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. GET redirects the customer back; POST answers {"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/app
  • POST /api/stores/shopify/compliance
  • POST /api/stores/shopify/rates
  • POST /api/salla/events
  • POST /api/salla/link
  • POST /api/zid/events
  • POST /api/merchant/verify
  • POST /api/parcel/create-with-security
  • GET /api/parcel/list-with-security
  • GET /api/parcel/tracking-logs/{track_id}
  • POST /api/secret/cheack
  • POST /api/webhooks/lfl
  • POST /api/carriers/webhook/{token}
  • POST /api/payments/shamcash/webhook
  • GET /api/payments/paymera/callback