Webhooks
iwocaPay provides an ecommerce webhook so you can get live updates as your customers’ order statuses change.
Webhooks are configured at the transaction level: each order has its own webhook destination, and there is no account-wide setting.
To enable webhooks for an order, supply the webhook_url parameter when you create the order.
If you omit it, no webhook is sent for that order.
POST /api/lending/edge/ecommerce/seller/<supplier_uuid>/order/| Field | Type | Description |
|---|---|---|
webhook_url | String | The URL that iwocaPay sends the webhook payload to for this order. |
iwocaPay then sends a webhook to that URL every time the order’s status changes.
iwocaPay sends the payload as a POST request with a JSON body to your webhook_url.
{ "data": { "webhook_id": "f0b4c1e2-9a3d-4f5b-8c6e-1d2a3b4c5d6e", "event_type": "ORDER_STATUS_CHANGED", "order_id": "aa8cfc99-3853-4641-8856-3294433b7bb7", "amount": 278.22, "reference": "Service Mjolnir", "status": "CREATED" }}| Field | Type | Description |
|---|---|---|
webhook_id | String | A unique ID (UUIDv4) for this webhook delivery. |
event_type | String | The event that triggered the webhook. Currently always "ORDER_STATUS_CHANGED". |
order_id | String | A unique ID (UUIDv4) for the associated order. |
amount | Number | The monetary amount for the order, in GBP. |
reference | String | The order’s reference. |
status | Enum, options: "CREATED", "PENDING", "UNSUCCESSFUL", "APPROVED", and "SUCCESSFUL" | The status of the order (see Order Status). |
Every webhook request includes a signature so you can verify it came from iwocaPay and that the payload has not been tampered with.
The signature is an HMAC-SHA256 hash of the raw request body, keyed with your access token, then Base64-encoded. It is sent in the following header:
X-Iwocapay-Hmac-Sha256: <signature>To verify a webhook, recompute the signature over the raw request body using your access token, and compare it against the value in the X-Iwocapay-Hmac-Sha256 header.
If they don’t match, reject the request.
import base64import hashlibimport hmac
def is_signature_valid(raw_body: bytes, received_signature: str, token: str) -> bool: digest = hmac.new(token.encode("utf-8"), raw_body, hashlib.sha256).digest() expected_signature = base64.b64encode(digest).decode("utf-8")
# Use a constant-time comparison to avoid timing attacks. return hmac.compare_digest(expected_signature, received_signature)import {createHmac, timingSafeEqual} from "node:crypto";
function isSignatureValid(rawBody: string, receivedSignature: string, token: string): boolean { const expectedSignature = createHmac("sha256", token) .update(rawBody, "utf8") .digest("base64");
const expected = Buffer.from(expectedSignature); const received = Buffer.from(receivedSignature);
// Use a constant-time comparison to avoid timing attacks. return expected.length === received.length && timingSafeEqual(expected, received);}<?php
function is_signature_valid(string $raw_body, string $received_signature, string $token): bool{ if (empty($raw_body) || empty($received_signature)) { return false; }
$computed = base64_encode(hash_hmac('sha256', $raw_body, $token, true));
// hash_equals is a constant-time comparison, to avoid timing attacks. return hash_equals($computed, $received_signature);}