Skip to content

Webhooks

Ocrch delivers signed HTTP POST requests to your application backend when:

  1. An order status changes (to paid, expired, or cancelled).
  2. An unknown transfer arrives at one of your wallets (a transaction that couldn't be matched to any pending deposit).

Your webhook endpoint must:

  • Accept POST requests with Content-Type: application/json.
  • Verify the Ocrch-Signature header (see below).
  • Return HTTP 200 OK to acknowledge receipt.
  • Respond within a reasonable timeout (a few seconds).

If your endpoint does not return 200 OK, Ocrch retries the webhook with exponential back-off:

AttemptDelay
11 second
22 seconds
34 seconds
48 seconds
516 seconds
122048 seconds (~34 min)

After 12 attempts the webhook is not retried automatically, but an admin can trigger a manual resend via the Admin API.

All webhooks are signed with the merchant HMAC key using the same algorithm as the Service API. To verify:

  1. Read the Ocrch-Signature header: {timestamp}.{base64_signature}.
  2. Compute HMAC-SHA256("{timestamp}.{raw_json_body}", merchant_secret).
  3. Compare your computed HMAC (base64-encoded) to base64_signature.
  4. Optionally, reject signatures where timestamp is too far in the past.
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyWebhookSignature(
rawBody: string,
signatureHeader: string,
merchantSecret: string
): boolean {
const [timestamp, receivedSig] = signatureHeader.split(".");
if (!timestamp || !receivedSig) return false;
const message = `${timestamp}.${rawBody}`;
const expected = createHmac("sha256", merchantSecret)
.update(message)
.digest("base64");
try {
return timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSig));
} catch {
return false;
}
}
// Express.js example
app.post("/webhooks/ocrch", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["ocrch-signature"] as string;
if (!verifyWebhookSignature(req.body.toString(), sig, process.env.MERCHANT_SECRET!)) {
return res.sendStatus(401);
}
const payload = JSON.parse(req.body.toString());
// Handle payload...
res.sendStatus(200);
});

Sent when an order transitions to paid, expired, or cancelled.

Header: Ocrch-Signature: {timestamp}.{base64_signature}

Body:

{
"event_type": "order_status_changed",
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"merchant_order_id": "your-order-123",
"status": "paid",
"amount": "19.99",
"timestamp": 1711900800
}
FieldTypeDescription
event_typestringAlways "order_status_changed"
order_idUUID stringInternal Ocrch order ID
merchant_order_idstringYour original order identifier
statusstringNew status: "paid", "expired", or "cancelled"
amountstringPayment amount (decimal string)
timestampintegerUnix timestamp when the event was emitted

Configuration: Set webhook_url per-order when calling the Service API create order endpoint.

Sent when a transfer arrives at one of your wallets but cannot be matched to any pending deposit. Useful for detecting overpayments, test transactions, or manual payments.

Body:

{
"event_type": "unknown_transfer",
"transfer_id": 42,
"blockchain": "eth",
"timestamp": 1711900800
}
FieldTypeDescription
event_typestringAlways "unknown_transfer"
transfer_idintegerInternal transfer record ID
blockchainstringChain identifier (e.g. "eth", "tron")
timestampintegerUnix timestamp when the event was emitted

Configuration: Set merchant.unknown_transfer_webhook_url in ocrch-config.toml.


Webhooks may be delivered more than once in edge cases (retries after a transient failure where your server returned 200 but the response was lost). Always process webhooks idempotently — check whether you have already processed a given order_id / transfer_id before taking action.


Admins can trigger a manual resend of any webhook via the Admin API:

Terminal window
# Resend order status webhook
curl -X POST https://checkout-api.example.com/api/v1/admin/orders/{order_id}/resend-webhook \
-H "Ocrch-Admin-Authorization: your-admin-secret"
# Resend unknown transfer webhook
curl -X POST https://checkout-api.example.com/api/v1/admin/transfers/{transfer_id}/resend-webhook \
-H "Ocrch-Admin-Authorization: your-admin-secret"

See the Admin API reference for full details.