Audience: External merchant engineering teams. Status: Production. Version: v1.3 Last updated: 2026-05.

1. Introduction

The Akwaan Express B2B Integration lets merchants:

Integration is bidirectional:

DirectionChannelTrigger
Merchant → AkwaanREST API call to /api/merchant/b2b/...The merchant initiates (e.g. creates an order).
Akwaan → MerchantHTTPS POST to merchant-configured webhook URLAkwaan initiates whenever an order status changes.

Supported capabilities

2. Architecture overview

+---------------------+         +---------------------------+
|  Merchant System    |  REST   |  Akwaan Express Backend   |
|                     +-------->+  /api/merchant/b2b/...    |
|                     |         |                           |
|  Webhook Listener   |<--------+  IntegrationService       |
|  (your URL)         |  POST   |  BroadcastOrderDetailsBulk|
+---------------------+         +---------------------------+

3. Authentication

3.1 API Key

All requests to /api/merchant/b2b/* MUST include:

HeaderValue
ApiKeyThe API key issued to your merchant account

The API key is mapped to exactly one merchant. The backend uses it both to authenticate the caller and to scope the request to that merchant's data. Merchants cannot read or modify another merchant's resources.

3.2 Example

POST /api/merchant/b2b/order HTTP/1.1
Host: api.akwanexpress.com
Content-Type: application/json
ApiKey: akx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

{ ... order body ... }

3.3 Failure modes

ConditionHTTPResponse body
Header missing400{ "code": 400, "message": "API Key is missing" }
Header present but unknown / revoked400{ "code": 400, "message": "Invalid API key" }
Header present, merchant deleted400Same as above

Security note: API keys are treated as bearer credentials. Treat them like passwords — never log them, never commit them, rotate on suspected leak. Contact Akwaan operations to issue a replacement and revoke the old key.

3.4 Recommended client hygiene

4. Outbound webhook integration

4.1 Configuration

Provide Akwaan operations with:

FieldDescription
UrlYour webhook base URL (e.g. https://api.example.com). Akwaan will POST to {Url}/api/akwaan/order-delivery/webhook.
AuthorizationKey(Optional) A custom header name Akwaan will send on every webhook (e.g. X-Webhook-Secret).
AuthorizationValue(Optional) The header value. Use this as a shared secret to verify the request came from Akwaan.
HasOTPWhether your orders require customer OTP verification before delivery.

4.2 Lifecycle

  1. An order's status changes inside Akwaan (e.g. delegate marks Delivered).
  2. The status transition triggers IntegrationService.BroadcastOrderDetailsBulk.
  3. Events are batched by merchant — each merchant receives one HTTP POST carrying all the events that concern them.
  4. Events for non-broadcastable statuses are silently dropped (see §5).
  5. The HTTP POST is dispatched with up to 3 attempts, 15s timeout per attempt, linear backoff (2s, 4s).
  6. A 2xx response = delivered. A 4xx response (except 408/429) = abandoned (your error, no point retrying). A 5xx / network error / timeout = retried up to the limit.

4.3 Retry behavior

ResponseRetried?
2xxNo (success).
408 Request TimeoutYes.
429 Too Many RequestsYes.
4xx (other)No (assumed permanent — fix your endpoint).
5xxYes.
Network error / DNS / TLS failureYes.
Timeout (15s)Yes.

After 3 failed attempts the event is abandoned. There is no built-in catch-up mechanism today; if you suspect missed events, contact operations to schedule a replay or use the manual GET /api/merchant/b2b/order/{externalId} lookup (see §14 for a future bulk sync endpoint suggestion).

4.4 Timing & ordering guarantees

4.5 Webhook security

5. Broadcasted order statuses

Akwaan broadcasts webhooks only for the 20 statuses below. Any other internal status transition does NOT generate a webhook.

status (int)statusKeyEnglishArabicMeaning
0PendingPendingقيد الانتظارOrder created, awaiting pickup assignment.
1InPickUpShipmentIn Pickup Shipmentفي قائمة الاستلامAdded to a pickup manifest assigned to a delegate.
4ReceivedReceivedتم الاستلامPicked up from the merchant.
5NotReceivedNot Receivedلم يتم الاستلامPickup attempted but failed (merchant unavailable, refused, etc).
6InWarehouseIn Warehouseفي المخزنArrived at warehouse, awaiting delivery routing.
7InDeliveryShipmentIn Delivery Shipmentفي قائمة التسليمLoaded onto a delivery manifest.
8InDeliveryProgressIn Delivery Progressجاري التسليمDelegate is actively delivering.
10DeliveredDeliveredتم التسليمSuccessfully delivered to customer.
12CancelledCancelledملغيCancelled before completion.
13CompletedCompletedمكتملFinal accounting state — money settled.
14RescheduledInWarehouseRescheduled In Warehouseإعادة جدولة في المخزنFailed delivery; back to warehouse for re-routing.
15RescheduledDelegateRescheduled With Delegateإعادة جدولة مع المندوبFailed delivery; delegate will retry.
16RefundedInWarehouseRefunded In Warehouseمرتجع في المخزنRefund initiated; package at warehouse.
17RefundedDelegateRefunded With Delegateمرتجع مع المندوبRefund initiated; delegate carrying it.
19WarehouseTransferWarehouse Transferتحويل بين مخازنMoving between warehouses.
27InRefundShipmentIn Refund Shipmentفي قائمة الإرجاعOn a refund manifest heading back to merchant.
28InRefundProgressIn Refund Progressجاري الإرجاعRefund delegate actively delivering back to merchant.
29RefundedToMerchantRefunded To Merchantتم الإرجاع للتاجرRefund handed back to merchant.
30WarehouseTransferRefundWarehouse Transfer (Refund)تحويل بين مخازن (مرتجع)Refund package moving between warehouses.
31WarehouseTransferRefundDelegateWarehouse Transfer Refund Delegateتحويل مرتجع مع المندوبRefund package with a transfer delegate.

Stability contract:

6. Webhook payload structure

6.1 Top-level shape

The webhook body is a JSON array of events. One HTTP POST may contain multiple events when they concern the same merchant.

[
  {
    "EventType": "OrderStatusUpdated",
    "EventData": { ... }
  },
  {
    "EventType": "OTPSend",
    "EventData": { ... }
  }
]
FieldTypeRequiredDescription
EventTypestringyesOne of OrderStatusCreated, OrderStatusUpdated, OTPSend, OTPResend.
EventDataobjectyesEvent-specific payload, see below.

6.2 EventData for OrderStatusCreated / OrderStatusUpdated

{
  "orderId": "MERCHANT-EXTERNAL-ID-123",
  "status": 10,
  "statusKey": "Delivered",
  "statusNameEn": "Delivered",
  "statusNameAr": "تم التسليم",
  "eventTimestamp": "2026-05-20T13:42:11.4321Z"
}
FieldTypeNullableDescription
orderIdstringyesThe merchant-provided ExternalId from order creation. Use this to correlate with your own DB.
statusintegernoNumeric value of OrderStatus. Stable.
statusKeystringnoEnum identifier. Stable.
statusNameEnstringnoEnglish display name. Display-only.
statusNameArstringnoArabic display name. Display-only.
eventTimestampISO 8601 UTCnoWhen Akwaan generated the event.

6.3 EventData for OTPSend

Same shape as the status events, plus an otp field:

{
  "orderId": "MERCHANT-EXTERNAL-ID-123",
  "status": 8,
  "statusKey": "InDeliveryProgress",
  "statusNameEn": "In Delivery Progress",
  "statusNameAr": "جاري التسليم",
  "eventTimestamp": "2026-05-20T13:42:11.4321Z",
  "otp": "4821"
}

6.4 EventData for OTPResend

The resend event carries only the OTP and order id (no status, since the order's status has not changed):

{
  "orderId": "MERCHANT-EXTERNAL-ID-123",
  "otp": "4821",
  "eventTimestamp": "2026-05-20T13:42:11.4321Z"
}

6.5 Required webhook response

Akwaan considers the delivery successful if your endpoint returns:

The response body is ignored. Return 200 OK with an empty body as the conventional ack.

7. Inbound REST API endpoints

Base path: /api/merchant/b2b

7.1 GET /wallets

Returns the merchant's wallet balances.

Request bodyResponseAuth
None200 OK with array of NewWalletDtoApiKey header required

7.2 POST /order

Create a new order in Akwaan.

Request body — OrderCreationIntegrationForm:

FieldTypeRequiredNotes
CodestringyesYour internal code. Must be unique within your merchant.
PinstringnoSecondary identifier for the order — an additional code printed on the package alongside Code. Used as a fallback reference when the primary code cannot be scanned or read. Treat it as a second order code, not an OTP.
CustomerIdstringnoYour internal customer id.
CustomerNamestringyes
CustomerPhoneNumberstringyes
CustomerSecondPhoneNumberstringno
ExternalIdstringyesThe id Akwaan will quote back to you in every webhook. Make it stable.
ContentstringyesDescription of package contents.
DeliveryZoneNamestringyesMust match an Akwaan zone name in the destination governorate. Resolve via § Reference data lookups.
PickupZoneNamestringyesMust match an Akwaan zone name in the source governorate.
PickUpGovernorateIdintyesResolve the id via § Reference data lookups.
DeliveryGovernorateIdintyesResolve the id via § Reference data lookups.
Notestringno
AmountdecimalyesThe order amount the customer pays on delivery, excluding the delivery fee. The delivery fee is calculated separately by Akwaan based on the destination zone — do not include it here.
NearestLandmarkstringno
SizeenumnoSmall (default), Medium, Large.
IsPaidboolnoSet to true when the delivery fee for this order has already been paid by the merchant (e.g. prepaid online). When true, the delivery fee is not collected from the customer and is instead deducted from the merchant's settlement payout. Defaults to false (customer pays the delivery fee on delivery).
PickUpLocationobjectno{ "lat": ..., "lng": ... }.
DeliveryLocationobjectno{ "lat": ..., "lng": ... }.

Response: ExternalOrderDto containing the order's Akwaan id, code, status, and echo of submitted fields.

7.3 PUT /merchant

Update merchant profile.

7.4 POST /withdraw-request

Submit a withdraw request against the merchant's wallet.

7.5 GET /order/{externalId}

Retrieve one of your own orders by the ExternalId you supplied when creating it. Returns every field you originally submitted plus the order's current status and full status history.

Request:

GET /api/merchant/b2b/order/MERCHANT-EXTERNAL-ID-123 HTTP/1.1
Host: api.akwanexpress.com
ApiKey: akx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Path parameters:

NameTypeRequiredNotes
externalIdstringyesThe exact value you sent as ExternalId on order creation. URL-encode if it contains special characters.

Authorization & scope:

Response shape — B2BOrderDetailDto:

FieldTypeNotes
idguidAkwaan-side order id.
codestringYour internal order code.
pinstringSecondary identifier you supplied at creation.
customerIdstringYour internal customer id (if you sent one).
customerNamestring
customerPhoneNumberstringNormalized to 07xxxxxxxx server-side.
customerSecondPhoneNumberstring?
externalIdstringEchoed back from your creation request.
contentstringDescription of contents.
notestring?
nearestLandmarkstring?
pickupZoneName / deliveryZoneNamestring
pickupGovernorate / deliveryGovernorateobject{ id, name }
pickUpLocation / deliveryLocationobject?{ lat, long } if provided.
amountdecimalOrder amount excluding delivery fee.
paidAmountdecimal
totalAmountdecimalamount + deliveryFee.
isPaidboolTrue when delivery fee was prepaid by the merchant.
sizeenumSmall / Medium / Large.
statusintCurrent status, numeric. See §5.
statusKeystringCurrent status, stable enum key.
statusNameEn / statusNameArstringDisplay-only.
creationDateUTC datetime
lastUpdateDateUTC datetime
statusHistoryarraySorted ascending by changedAt. See shape below.

statusHistory[] entry shape:

{
  "fromStatus": 0,
  "fromStatusKey": "Pending",
  "fromStatusNameEn": "Pending",
  "fromStatusNameAr": "قيد الانتظار",
  "toStatus": 1,
  "toStatusKey": "InPickUpShipment",
  "toStatusNameEn": "In Pickup Shipment",
  "toStatusNameAr": "في قائمة الاستلام",
  "changedAt": "2026-05-20T11:00:00.123Z",
  "shipmentCode": "SHP-2026-0042",
  "note": null
}

Sample successful response:

{
  "code": 200,
  "message": "Operation successful",
  "data": {
    "id": "8a7b6c5d-1234-5678-9abc-def012345678",
    "code": "SHOP-9912",
    "pin": "4821",
    "customerId": "CUST-7700",
    "customerName": "Ahmed Ali",
    "customerPhoneNumber": "07701234567",
    "externalId": "MERCHANT-EXTERNAL-ID-123",
    "content": "Mobile phone case",
    "pickupZoneName": "Mansour",
    "deliveryZoneName": "Karrada",
    "pickupGovernorate": { "id": 1, "name": "Baghdad" },
    "deliveryGovernorate": { "id": 1, "name": "Baghdad" },
    "amount": 25000,
    "paidAmount": 0,
    "totalAmount": 28000,
    "isPaid": false,
    "size": "Small",
    "status": 10,
    "statusKey": "Delivered",
    "statusNameEn": "Delivered",
    "statusNameAr": "تم التسليم",
    "creationDate": "2026-05-20T10:55:00.123Z",
    "lastUpdateDate": "2026-05-20T13:42:11.432Z",
    "statusHistory": [
      {
        "fromStatus": 0, "fromStatusKey": "Pending",
        "toStatus":   1, "toStatusKey":   "InPickUpShipment",
        "changedAt": "2026-05-20T11:00:00.000Z",
        "shipmentCode": "PCK-0042"
      },
      {
        "fromStatus": 1, "fromStatusKey": "InPickUpShipment",
        "toStatus":   4, "toStatusKey":   "Received",
        "changedAt": "2026-05-20T11:45:12.000Z",
        "shipmentCode": "PCK-0042"
      },
      {
        "fromStatus": 8, "fromStatusKey": "InDeliveryProgress",
        "toStatus":  10, "toStatusKey":   "Delivered",
        "changedAt": "2026-05-20T13:42:11.000Z",
        "shipmentCode": "DLV-0091"
      }
    ]
  }
}

Error responses:

HTTPBody messageCause
400ExternalId is requiredEmpty / whitespace path value.
401API Key is missingNo ApiKey header.
400Merchant Integration Not FoundUnknown / revoked ApiKey.
404Order not foundNo order with that ExternalId belongs to the calling merchant. (Includes the case where another merchant owns an order with this id.)

8. Reference data lookups (governorates & zones)

These read-only endpoints expose the system-wide reference data merchants need to populate PickUpGovernorateId / DeliveryGovernorateId / PickupZoneName / DeliveryZoneName when creating orders. The data is identical for every merchant and updated by Akwaan operations whenever new coverage is added.

Both endpoints:

§1 GET /governorates

Paginated list of all governorates.

Query paramTypeRequiredNotes
namestringnoOptional substring filter on the governorate name.
pageNumberintnoDefaults to 1.
pageSizeintnoDefaults to the API standard page size.

Response data shape:

{
  "code": 200,
  "message": "Operation successful",
  "data": [
    { "id": 1, "name": "Baghdad" },
    { "id": 2, "name": "Basra" },
    { "id": 3, "name": "Erbil" }
  ],
  "pagination": {
    "currentPage": 1,
    "pageSize": 20,
    "totalItems": 18,
    "totalPages": 1
  }
}

The id values returned here are the same integers you pass as PickUpGovernorateId / DeliveryGovernorateId on order creation.

§2 GET /zones

Paginated list of zones (also called districts). Filter by governorate to narrow the list.

Query paramTypeRequiredNotes
governorateIdintnoRecommended. Returns only zones inside the given governorate.
governorateNamestringnoAlternative to governorateId for callers that only know the name.
namestringnoOptional substring filter on the zone name.
typeintno1 = Center, 2 = Outskirts. Optional.
sectorIdintnoOptional sector filter (internal Akwaan grouping).
pageNumberintnoDefaults to 1.
pageSizeintnoDefaults to the API standard page size.

Response data shape:

{
  "code": 200,
  "message": "Operation successful",
  "data": [
    {
      "id": 12,
      "name": "Karrada",
      "governorate": { "id": 1, "name": "Baghdad" }
    },
    {
      "id": 13,
      "name": "Mansour",
      "governorate": { "id": 1, "name": "Baghdad" }
    }
  ],
  "pagination": {
    "currentPage": 1,
    "pageSize": 20,
    "totalItems": 47,
    "totalPages": 3
  }
}

The name string is what you should send as PickupZoneName / DeliveryZoneName on order creation. Zone names are unique within a governorate; match them exactly (including casing and Arabic characters) when populating the order form.

§3 Recommended usage pattern

  1. Cache GET /governorates at the start of each session — the list is short and rarely changes.
  2. For each pickup/delivery selection, call GET /zones?governorateId=<id> to get the available zones for that governorate.
  3. Persist the resolved governorateId and the verbatim zone name on your side keyed by your own internal region IDs.
  4. Re-fetch on a low cadence (e.g. once per day) to pick up new zones as Akwaan expands coverage.

Why zone names instead of zone IDs on order creation? Historical compatibility with legacy merchant integrations. New integrations are welcome to ignore the id field and only carry the name on their side — the order endpoint resolves the name + governorate id back to a zone server-side.

§4 Example calls

GET /api/merchant/b2b/governorates HTTP/1.1
Host: api.akwanexpress.com
GET /api/merchant/b2b/zones?governorateId=1&pageSize=100 HTTP/1.1
Host: api.akwanexpress.com

9. Request / response examples

9.1 Create order — success

Request:

POST /api/merchant/b2b/order HTTP/1.1
ApiKey: akx_live_xxx
Content-Type: application/json

{
  "Code": "SHOP-9912",
  "ExternalId": "MERCHANT-EXTERNAL-ID-123",
  "CustomerName": "Ahmed Ali",
  "CustomerPhoneNumber": "07701234567",
  "Content": "Mobile phone case",
  "DeliveryZoneName": "Karrada",
  "PickupZoneName": "Mansour",
  "PickUpGovernorateId": 1,
  "DeliveryGovernorateId": 1,
  "Amount": 25000,
  "Size": "Small"
}

Response — 200 OK:

{
  "code": 200,
  "message": "Operation successful",
  "data": {
    "id": "8a7b6c5d-1234-5678-9abc-def012345678",
    "code": "SHOP-9912",
    "customerName": "Ahmed Ali",
    "customerPhoneNumber": "07701234567",
    "externalId": "MERCHANT-EXTERNAL-ID-123",
    "amount": 25000,
    "status": "Pending"
  }
}

9.2 Create order — validation error

{
  "code": 400,
  "message": "Delivery Zone or Pickup Zone cant be null or empty",
  "errors": null
}

9.3 Authentication failure

POST /api/merchant/b2b/order HTTP/1.1
Content-Type: application/json
(no ApiKey header)
{
  "code": 400,
  "message": "API Key is missing"
}

9.4 Webhook — single OrderStatusUpdated event

POST /api/akwaan/order-delivery/webhook HTTP/1.1
Host: api.your-shop.com
Content-Type: application/json; charset=utf-8
X-Webhook-Secret: <your-configured-shared-secret>

[
  {
    "EventType": "OrderStatusUpdated",
    "EventData": {
      "orderId": "MERCHANT-EXTERNAL-ID-123",
      "status": 10,
      "statusKey": "Delivered",
      "statusNameEn": "Delivered",
      "statusNameAr": "تم التسليم",
      "eventTimestamp": "2026-05-20T13:42:11.4321Z"
    }
  }
]

9.5 Webhook — mixed batch (status + OTP)

[
  {
    "EventType": "OrderStatusUpdated",
    "EventData": {
      "orderId": "MERCHANT-EXTERNAL-ID-100",
      "status": 8,
      "statusKey": "InDeliveryProgress",
      "statusNameEn": "In Delivery Progress",
      "statusNameAr": "جاري التسليم",
      "eventTimestamp": "2026-05-20T13:42:11.4321Z"
    }
  },
  {
    "EventType": "OTPSend",
    "EventData": {
      "orderId": "MERCHANT-EXTERNAL-ID-100",
      "status": 8,
      "statusKey": "InDeliveryProgress",
      "statusNameEn": "In Delivery Progress",
      "statusNameAr": "جاري التسليم",
      "eventTimestamp": "2026-05-20T13:42:11.5023Z",
      "otp": "4821"
    }
  }
]

10. Error handling

10.1 Response envelope

All API responses use the standard envelope:

{
  "code": 200,
  "message": "Operation successful",
  "data": { ... },
  "pagination": { ... },
  "errors": [ ... ]
}
FieldTypeWhen present
codeintAlways (HTTP status mirror).
messagestringAlways.
dataTOn success.
paginationobjectOn paginated list responses.
errorsstring[]On validation failures.

10.2 Common error codes

HTTPCauseAction
400Missing ApiKey header, malformed body, validation failure, business rule violation.Inspect message / errors.
401 / 403Currently not used — the API key flow returns 400 on auth failure.
404Resource not found (order, zone, merchant).Verify ids.
500Unexpected server error.Retry with backoff; contact operations if persistent.

10.3 Webhook receiver error handling on your side

11. Security best practices

API key protection

Webhook validation

Replay protection

Timeout / availability

Recommended rate limits on your endpoint

12. End-to-end integration flow

1. Onboarding
   ├─ Akwaan issues ApiKey + records your webhook URL & auth header
   └─ You whitelist Akwaan in your firewall (optional)

2. Order creation
   ├─ POST /api/merchant/b2b/order  ──→  Akwaan creates order
   └─ Akwaan webhook: OrderStatusCreated (status=0, Pending)

3. Lifecycle
   ├─ Delegate picks up         → webhook: Received (4)
   ├─ Arrives at warehouse      → webhook: InWarehouse (6)
   ├─ Loaded for delivery       → webhook: InDeliveryShipment (7)
   ├─ Out for delivery          → webhook: InDeliveryProgress (8)
   ├─ OTP sent (if applicable)  → webhook: OTPSend
   └─ Delivered                 → webhook: Delivered (10)

4. Settlement
   ├─ Money reconciled          → webhook: Completed (13)
   └─ Wallet balance updated   ← GET /api/merchant/b2b/wallets

5. Refund path (if needed)
   ├─ Failed delivery           → webhook: RescheduledInWarehouse (14) etc.
   ├─ Refund manifest           → webhook: InRefundShipment (27)
   └─ Returned to merchant      → webhook: RefundedToMerchant (29)

6. Error recovery
   ├─ Missed webhook?           → GET /api/merchant/b2b/order/{externalId} (manual lookup)
   └─ Suspect downtime?         → contact operations for replay

13. Testing recommendations

Sandbox

Request a sandbox API key + a sandbox webhook URL from Akwaan operations. The sandbox mirrors production schemas but uses isolated data.

Webhook receiver testing

  1. Stand up a temporary webhook receiver locally:
    • ngrok or webhook.site make this trivial.
    • Configure the public URL with Akwaan as your sandbox webhook URL.
  2. Create test orders via POST /api/merchant/b2b/order.
  3. Trigger status transitions (Akwaan ops can step a sandbox order through the lifecycle).
  4. Verify your handler:
    • Authenticates the header.
    • Parses every payload shape.
    • Is idempotent (replay the same event and assert no double-write).
    • Returns 2xx within 15s.
  5. Failure cases to exercise:
    • Your endpoint returns 500 → Akwaan retries up to 3 times.
    • Your endpoint takes 30s → first attempt times out, retry.
    • Your endpoint returns 401 → no retry (assumed permanent).

Load testing

In sandbox, ask operations to bulk-transition 1000+ orders. Verify your endpoint sustains the burst.

14. Edge cases & expected behavior

ScenarioBehavior
Status transition not in the broadcast list (e.g. InPickUpProgress, DelegateChanged).No webhook sent. Document expected statuses on your side.
Same status fires twice in the same transaction.Akwaan de-duplicates (orderId, eventType) within a single broadcast batch. Across batches, duplicates can still arrive — be idempotent.
Order created without an IntegrationDetails record.No webhooks sent for that order. (Indicates an ops misconfiguration — contact us.)
Merchant URL is malformed.Logged and skipped; no retries. Fix in the merchant profile.
Merchant endpoint down for 3 attempts.Event abandoned. Use the lookup endpoint to catch up.
ExternalId is empty.The webhook still fires but orderId will be null. Always set ExternalId on order creation.
Webhook receives an unknown statusKey (future status).Treat as informational. Log and ignore. Akwaan will document any new broadcastable statuses in the changelog (§15).
Webhook receives a status numerically equal but with new statusKey semantics.This will never happen — both status (int) and statusKey (string) are stable.

15. Versioning & changelog

This document describes integration version v1.

Breaking changes (renamed fields, changed semantics of existing values, removed statuses) will be announced via email and a new major version (v2). Additive changes (new fields, new broadcastable statuses) will be released in minor version notes — your handler should already tolerate them.

Changelog

DateChange
2026-05v1.3: Added GET /api/merchant/b2b/order/{externalId} (authenticated). Returns the full order detail merchants originally submitted plus the order's current status and complete status history. Strictly scoped to the calling merchant by ApiKey. See §7.5. No breaking changes.
2026-05v1.2: Added two public read-only reference-data endpoints to the B2B API: GET /api/merchant/b2b/governorates and GET /api/merchant/b2b/zones. Both are unauthenticated (no ApiKey required) and return only the minimal fields merchants need: { id, name, governorate: { id, name } }. See the new Reference data lookups section. No breaking changes.
2026-05v1.1: Webhook payloads now include rich status descriptor (status, statusKey, statusNameEn, statusNameAr, eventTimestamp). The legacy Status string field is removed in favor of statusKey. Broadcast filter restricted to 20 explicit statuses (see §5).
2026-05v1.0: Initial release.

For credential issuance, integration support, or to report a webhook delivery issue, contact Akwaan operations.