Webhooks

Outbound webhooks

Wijha POSTs (or PUT/PATCH/GET) JSON events to your URL when store data changes. Optional secrets sign every delivery so you can verify authenticity.

Manage endpoints

Open Merchant Portal → Store settings → Developers → Webhooks. Each endpoint has:

  • Nickname — optional label shown in the portal.
  • URL — HTTPS destination for deliveries.
  • Method — POST (default), PUT, PATCH, or GET.
  • Event — one or more event types (or * for all).
  • Secret — optional shared secret (min 8 characters) used for HMAC signatures.

You can also create endpoints via POST /api/v1/webhooks/endpoints with the webhooks.write scope.

Delivery behavior

When a subscribed event fires, Wijha enqueues a delivery and sends it promptly:

  • Up to 6 attempts total (1 initial + 5 retries).
  • Retry delays: 30s → 2m → 10m → 1h → 6h.
  • Continuous failure for about one week deactivates the endpoint and notifies the store.

Update the URL or reactivate under Developers → Webhooks after fixing your receiver.

Request headers

Every delivery includes:

  • Content-Type: application/json
  • User-Agent: Wijha-Webhooks/1.0
  • X-Wijha-Timestamp — Unix milliseconds at send time
  • X-Wijha-Signature — present only when a secret is set: HMAC-SHA256 hex of `${timestamp}.${rawBody}`
Verify signature (Node.js)
const crypto = require("crypto");

function verifyWijhaSignature({ secret, timestamp, rawBody, signature }) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "utf8"),
    Buffer.from(signature, "utf8"),
  );
}

// Express example — keep the raw body for HMAC verification
app.post("/webhooks/wijha", express.raw({ type: "application/json" }), (req, res) => {
  const timestamp = req.get("X-Wijha-Timestamp");
  const signature = req.get("X-Wijha-Signature");
  const rawBody = req.body.toString("utf8");
  if (!verifyWijhaSignature({
    secret: process.env.WIJHA_WEBHOOK_SECRET,
    timestamp,
    rawBody,
    signature,
  })) {
    return res.status(401).send("invalid signature");
  }
  const event = JSON.parse(rawBody);
  // handle event.eventType + event.data / event.changes
  res.status(200).send("ok");
});

Delivery JSON

Each attempt sends this envelope. data holds full entity snapshots; changes holds the sparse event fields.

{
  "id": "del_01JABCXYZ",
  "event": "OrderCreated",
  "eventType": "OrderCreated",
  "storeId": "str_01JSTORE",
  "occurredAt": "2026-10-08T17:12:04.128Z",
  "data": {
    "order": {
      "id": "ord_01JABC",
      "storeId": "str_01JSTORE",
      "number": "10042",
      "channel": "portal",
      "status": "pending",
      "customerId": "cus_01JDEF",
      "customerName": "Ahmed Hassan",
      "customerPhone": "+201012345678",
      "shippingAddress": {
        "city": "Cairo",
        "line1": "12 Nile St",
        "phone": "+201012345678"
      },
      "lineItems": [
        {
          "productId": "prd_01J",
          "variantId": "var_01J",
          "sku": "TEE-BLK-M",
          "title": "Cotton tee",
          "qty": 2,
          "price": 225
        }
      ],
      "payment": { "method": "cod", "codAmount": 495, "currency": "EGP" },
      "totals": {
        "subtotal": 450,
        "shipping": 45,
        "total": 495,
        "currency": "EGP"
      },
      "createdAt": "2026-10-08T17:12:04.000Z"
    },
    "customer": {
      "id": "cus_01JDEF",
      "name": "Ahmed Hassan",
      "phone": "+201012345678",
      "email": "ahmed@example.com"
    }
  },
  "changes": {
    "aggregateType": "order",
    "aggregateId": "ord_01JABC"
  }
}

Only related keys appear in data for a given event. Respond with HTTP 2xx to acknowledge.

Enrichment

Before enqueueing, Wijha expands the sparse domain event:

  • id / event / eventType / storeId / occurredAt — delivery metadata.
  • data — full snapshots (order, shipment, product, customer, collection, stockLevel as applicable).
  • changes — original sparse facts (aggregateType, aggregateId, status transitions, actions, errors).

If a related entity cannot be loaded, that key is omitted from data; changes still arrive.

Event catalog

Subscribe to any of the following event types:

  • OrderCreated — new order
  • OrderUpdated — order fields changed
  • OrderStatusChanged — order status transition
  • ProductUpdated — product created, updated, or deleted
  • CollectionUpdated — collection created, updated, or deleted
  • CustomerCreated — new customer
  • CustomerUpdated — customer fields changed
  • ShipmentCreated — shipment booked
  • ShipmentDelivered — shipment marked delivered
  • ShipmentCreateFailed — booking failed
  • InventoryAdjusted — stock level adjusted
  • StockReceived — inbound stock received

OrderCreated example

Full order snapshot plus linked customer when available:

{
  "id": "del_01JABCXYZ",
  "event": "OrderCreated",
  "eventType": "OrderCreated",
  "storeId": "str_01JSTORE",
  "occurredAt": "2026-10-08T17:12:04.128Z",
  "data": {
    "order": {
      "id": "ord_01JABC",
      "storeId": "str_01JSTORE",
      "number": "10042",
      "channel": "portal",
      "status": "pending",
      "customerId": "cus_01JDEF",
      "customerName": "Ahmed Hassan",
      "customerPhone": "+201012345678",
      "shippingAddress": {
        "city": "Cairo",
        "line1": "12 Nile St",
        "phone": "+201012345678"
      },
      "lineItems": [
        {
          "productId": "prd_01J",
          "variantId": "var_01J",
          "sku": "TEE-BLK-M",
          "title": "Cotton tee",
          "qty": 2,
          "price": 225
        }
      ],
      "payment": { "method": "cod", "codAmount": 495, "currency": "EGP" },
      "totals": {
        "subtotal": 450,
        "shipping": 45,
        "total": 495,
        "currency": "EGP"
      },
      "createdAt": "2026-10-08T17:12:04.000Z"
    },
    "customer": {
      "id": "cus_01JDEF",
      "name": "Ahmed Hassan",
      "phone": "+201012345678",
      "email": "ahmed@example.com"
    }
  },
  "changes": {
    "aggregateType": "order",
    "aggregateId": "ord_01JABC"
  }
}

ProductUpdated example

{
  "id": "del_01JPROD",
  "event": "ProductUpdated",
  "eventType": "ProductUpdated",
  "storeId": "str_01JSTORE",
  "occurredAt": "2026-10-08T16:40:00.000Z",
  "data": {
    "product": {
      "id": "prd_01J",
      "title": "Cotton tee",
      "status": "active",
      "price": 225,
      "currency": "EGP",
      "sku": "TEE-BLK-M"
    }
  },
  "changes": {
    "aggregateType": "product",
    "aggregateId": "prd_01J",
    "action": "updated"
  }
}

CustomerCreated example

{
  "id": "del_01JCUS",
  "event": "CustomerCreated",
  "eventType": "CustomerCreated",
  "storeId": "str_01JSTORE",
  "occurredAt": "2026-10-08T15:05:22.000Z",
  "data": {
    "customer": {
      "id": "cus_01JDEF",
      "name": "Ahmed Hassan",
      "phone": "+201012345678",
      "email": "ahmed@example.com",
      "createdAt": "2026-10-08T15:05:22.000Z"
    }
  },
  "changes": {
    "aggregateType": "customer",
    "aggregateId": "cus_01JDEF"
  }
}

ShipmentCreated example

{
  "id": "del_01JSHIP",
  "event": "ShipmentCreated",
  "eventType": "ShipmentCreated",
  "storeId": "str_01JSTORE",
  "occurredAt": "2026-10-08T18:20:11.000Z",
  "data": {
    "shipment": {
      "id": "shp_01J",
      "orderId": "ord_01JABC",
      "trackingNumber": "WJH123456789",
      "status": "booked",
      "carrier": "mega"
    },
    "order": {
      "id": "ord_01JABC",
      "number": "10042",
      "status": "waiting_for_pickup"
    }
  },
  "changes": {
    "aggregateType": "shipment",
    "aggregateId": "shp_01J",
    "orderId": "ord_01JABC"
  }
}

Expected responses

Your endpoint should acknowledge quickly:

  • 2xx — delivery marked successful; consecutive failure window cleared.
  • Other status or timeout — counted as failure; retries follow the schedule above.
  • GET deliveries send the same headers; body is omitted for GET.
HTTP/1.1 200 OK
Content-Type: text/plain

ok