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.
Create a webhook endpoint with your HTTPS URL and the events you want to subscribe to.
Store the secret returned in the response — it's only shown once and is used to verify webhook signatures.
Implement signature verification in your endpoint handler (see below).
Manage Webhook Endpoints
Create, list, and delete webhook endpoints programmatically.
| Parameter | Type | Description |
|---|---|---|
urlrequired | string | Endpoint URL (must be HTTPS). |
eventsrequired | string[] | Array of event types to subscribe to. |
description | string | Optional description (max 200 characters). |
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"
}'{
"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
secret is only returned when the endpoint is created. Store it securely — you'll need it to verify webhook signatures.List all webhook endpoints. Secrets are masked in the response.
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.
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.
{
"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
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.
