Webhooks
Receive real-time notifications when invoice statuses change. All webhooks are signed with HMAC-SHA256 for security.
Available Events
invoice.createdNew invoice created
invoice.detectedPayment detected (0 confirmations)
invoice.partialPartial payment received
invoice.overpaidOverpayment received
invoice.confirmedPayment confirmed (5 blocks)
invoice.processing_payoutPayout in progress
invoice.paid_outFunds successfully distributed
invoice.expiredInvoice expired without payment
invoice.failedPayout failed
invoice.late_paymentPayment after expiration
Create Webhook Endpoint
/v1/webhooks/endpointsRegister a new webhook endpoint for your tenant
const response = await fetch('https://api.fromchain.io/v1/webhooks/endpoints', {
method: 'POST',
headers: {
'Authorization': 'Bearer gw_live_...',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://yourdomain.com/api/webhooks',
enabled: true,
eventsSubscribed: [
'invoice.confirmed',
'invoice.paid_out',
'invoice.failed'
]
})
});
const endpoint = await response.json();
// {
// "id": "wh_endpoint_abc123",
// "url": "https://yourdomain.com/api/webhooks",
// "secret": "whsec_xyz789...", // Store this securely!
// "enabled": true,
// "eventsSubscribed": ["invoice.confirmed", "invoice.paid_out", "invoice.failed"],
// "createdAt": "2025-12-18T10:00:00Z"
// }Verifying Webhook Signatures
Every webhook includes HMAC-SHA256 signature headers for verification:
POST /api/webhooks HTTP/1.1
Host: yourdomain.com
Content-Type: application/json
X-Webhook-Id: evt_abc123
X-Webhook-Timestamp: 1702905600000
X-Webhook-Signature: v1=a1b2c3d4e5f6...
X-Tenant-Id: tenant_xyz789
{
"id": "evt_abc123",
"type": "invoice.confirmed",
"createdAt": "2025-12-18T11:00:00.000Z",
"data": {
"invoiceId": "inv_123",
"status": "CONFIRMED",
...
}
}Verify the signature to ensure the webhook came from FromChain:
import crypto from 'crypto';
function verifyWebhook(
payload: string, // Raw request body
timestamp: string, // X-Webhook-Timestamp header
signature: string, // X-Webhook-Signature header
secret: string // Your endpoint secret
): boolean {
// Construct the signed payload
const signedPayload = `${timestamp}.${payload}`;
// Compute HMAC
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Compare signatures (constant-time comparison)
const receivedSignature = signature.replace('v1=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);
}
// Express.js example
app.post('/api/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'] as string;
const timestamp = req.headers['x-webhook-timestamp'] as string;
const payload = req.body.toString();
// Verify timestamp to prevent replay attacks
const now = Date.now();
const webhookTime = parseInt(timestamp);
if (Math.abs(now - webhookTime) > 5 * 60 * 1000) { // 5 minutes
return res.status(400).send('Timestamp too old');
}
// Verify signature
if (!verifyWebhook(payload, timestamp, signature, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const event = JSON.parse(payload);
console.log('Valid webhook received:', event.type);
res.status(200).send('OK');
});Retry Behavior
If your endpoint fails (non-2xx response), FromChain will retry with exponential backoff:
| Attempt | Delay | Total Time |
|---|---|---|
| 1 | Immediate | 0s |
| 2 | 30 seconds | 30s |
| 3 | 2 minutes | 2m 30s |
| 4 | 5 minutes | 7m 30s |
| 5 | 15 minutes | 22m 30s |
| 6 | 1 hour | 1h 22m |
| 7 | 3 hours | 4h 22m |
| 8 | 6 hours | 10h 22m |
⚠️ Important: After 8 failed attempts, the delivery moves to a dead-letter queue. You can manually replay failed deliveries via the API.
Webhook Management
/v1/webhooks/endpointsList all webhook endpoints
/v1/webhooks/endpoints/:idUpdate endpoint URL or subscribed events
/v1/webhooks/endpoints/:id/rotate-secretRotate the webhook secret
/v1/webhooks/endpoints/:id/testSend a test webhook event
/v1/webhooks/replay/:eventIdManually replay a specific webhook event
/v1/webhooks/deliveriesList webhook delivery attempts
Best Practices
✅ Always verify signatures
Never process webhooks without verifying the HMAC signature to prevent spoofing.
✅ Respond quickly
Return a 200 response immediately. Process the webhook asynchronously using a queue.
✅ Handle idempotency
Use the X-Webhook-Id header to deduplicate webhook deliveries.
✅ Use HTTPS endpoints
Webhook URLs must use HTTPS in production for security.