Skip to main content

Overview

Webhooks deliver HTTP POST requests to your endpoint whenever subscribed events occur. Each delivery is signed with HMAC-SHA256 so you can verify it came from the platform.

Supported Events

EventTrigger
order.createdA new order is placed by a client
order.status_changedAn order’s status changes (any transition)
order.cancelledAn order is cancelled
offer.updatedAn exchange rate offer is updated

Registering a Webhook

curl -X POST https://api.example.com/api/v1/partner/webhooks \
  -H "Authorization: Bearer sk_personal_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/hooks/exchange",
    "events": ["order.created", "order.status_changed"]
  }'
Response (secret shown once):
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "url": "https://your-app.com/hooks/exchange",
  "events": ["order.created", "order.status_changed"],
  "isActive": true,
  "secretKey": "a1b2c3d4...",
  "note": "Store this secret key securely — it will not be shown again."
}
Save secretKey immediately. Use it to verify HMAC-SHA256 signatures.

Verifying Signatures

Every delivery includes:
X-Webhook-Signature: sha256=<hmac>
X-Webhook-Event: order.created
X-Webhook-Delivery: <job-id>
Verify the signature before processing:
import * as crypto from 'crypto';

function verifySignature(rawBody: string, secret: string, signature: string): boolean {
  const expected = `sha256=${crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')}`;
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature),
  );
}

// Express example
app.post('/hooks/exchange', (req, res) => {
  const sig = req.headers['x-webhook-signature'] as string;
  const rawBody = JSON.stringify(req.body);

  if (!verifySignature(rawBody, process.env.WEBHOOK_SECRET!, sig)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process event
  const { event, data } = req.body;
  console.log(`Received ${event}:`, data);
  res.status(200).send('OK');
});

Payload Structure

All events share this envelope:
{
  "event": "order.created",
  "timestamp": "2026-02-26T12:00:00.000Z",
  "data": { /* event-specific object */ }
}

order.created

{
  "event": "order.created",
  "timestamp": "2026-02-26T12:00:00.000Z",
  "data": {
    "id": 1234,
    "status": "ORDERINIT",
    "amountSource": "1000",
    "amountDest": "26.5"
  }
}

order.status_changed

{
  "event": "order.status_changed",
  "timestamp": "2026-02-26T12:05:00.000Z",
  "data": { "id": 1234, "status": "CLIENTPAYMENTWAIT" },
  "status": "CLIENTPAYMENTWAIT"
}

order.cancelled

{
  "event": "order.cancelled",
  "timestamp": "2026-02-26T12:10:00.000Z",
  "data": { "id": 1234, "status": "CANCEL" }
}

Retry Policy

If your endpoint returns a non-2xx status or times out, the platform retries up to 5 times with exponential backoff:
AttemptDelay
15 seconds
210 seconds
320 seconds
440 seconds
580 seconds
After 10 consecutive failures, the webhook is automatically deactivated to protect your endpoint. You can reactivate it via the dashboard or API.

Testing Webhooks Locally

Use a tunnel tool to expose your local server:
# Using ngrok
ngrok http 3001

# Register the tunnel URL as your webhook
curl -X POST https://api.example.com/api/v1/partner/webhooks \
  -H "Authorization: Bearer sk_personal_YOUR_KEY" \
  -d '{"url":"https://abc123.ngrok.io/hooks","events":["order.created"]}'