Webhooks

Webhooks notify your application in real-time when events occur in Chain — payments completing, card authorizations, business verification updates, and more.

Setup

You can manage webhook endpoints via the API or from the Chain Dashboard.

  1. Create a webhook endpoint with your HTTPS URL and the events you want to subscribe to.
  2. Store the secret returned in the response — it's only shown once and is used to verify webhook signatures.
  3. Implement signature verification in your endpoint handler (see below).

Manage Webhook Endpoints

Create, list, and delete webhook endpoints programmatically.

POST/v1/webhooks

Parameter Type Description
url required string Endpoint URL (must be HTTPS).
events required string[] Array of event types to subscribe to.
description string Optional description (max 200 characters).

Create webhook endpoint

curl -X POST https://api.chain.com/v1/webhooks \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/chain",
    "events": ["funding.completed", "payout.completed", "card.authorization.request"],
    "description": "Production webhook handler"
  }'

Response

{
  "id": "wh_abc123",
  "url": "https://example.com/webhooks/chain",
  "events": ["funding.completed", "payout.completed", "card.authorization.request"],
  "description": "Production webhook handler",
  "status": "active",
  "secret": "whsec_abc123...",
  "created_at": "2026-01-15T10:00:00Z"
}

Store the secret

The secret is only returned when the endpoint is created. Store it securely — you'll need it to verify webhook signatures.

GET/v1/webhooks

List all webhook endpoints. Secrets are masked in the response.

DELETE/v1/webhooks?endpointId=wh_abc123

Delete a webhook endpoint. Pass the endpoint ID as a query parameter.

Signature Verification

Every webhook includes a X-Chain-Signature header containing an HMAC-SHA256 signature. Always verify this signature to confirm the event came from Chain.

Node.js

import crypto from 'crypto';

function verifyWebhook(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your route handler:
app.post('/webhooks/chain', (req, res) => {
  const signature = req.headers['x-chain-signature'];
  const isValid = verifyWebhook(
    JSON.stringify(req.body),
    signature,
    process.env.CHAIN_WEBHOOK_SECRET
  );

if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

// Process event...
  res.status(200).send('OK');
});

Event Types

Payments

Event Description
funding.initiated A funding request has been created.
funding.completed Funds have been received and converted.
funding.failed A funding request has failed.
payout.initiated A payout has been created.
payout.completed Payout has been delivered to the recipient.
payout.failed A payout has failed.

Cards

Event Description
card.created A new card has been issued.
card.authorization.request Real-time authorization request (respond within 2s).
card.transaction.completed A card transaction has settled.
card.frozen A card has been frozen.
card.cancelled A card has been cancelled.

Business & Compliance

Event Description
business.verification.approved Business KYB verification approved.
business.verification.rejected Business KYB verification rejected.
compliance.screening.flagged A compliance screening flagged an issue.
report.ready A generated report is ready for download.

Payload Format

All webhook payloads follow a consistent structure.

Webhook payload

{
  "id": "evt_abc123",
  "type": "funding.completed",
  "created_at": "2026-01-15T10:30:00Z",
  "data": {
    "id": "fund_123abc",
    "status": "completed",
    "amount": 10000,
    "currency": "USD",
    "target_amount": 9985.50,
    "target_currency": "USDC"
  }
}

Retry Policy

Idempotency

Your webhook handler should be idempotent. Chain may deliver the same event more than once. Use the id field to deduplicate.

Attempt Delay
1st retry 1 minute
2nd retry 5 minutes
3rd retry 30 minutes
4th retry 2 hours
5th retry 24 hours

After 5 failed attempts, the webhook is marked as failed and the endpoint may be disabled. You can retry failed webhooks from the Dashboard.