Webhooks
Webhooks allow you to receive real-time notifications about invoice status changes. This document explains how to set up and use webhooks with the SplitRoute API.
Setting Up Webhooks
To set up a webhook, include the webhook_url
and webhook_secret
parameters when creating an invoice:
JSON
1 { 2 "nominal_amount": 10.00, 3 "nominal_currency": "USD", 4 "destinations": [ 5 { 6 "account": "nano_1abc...", 7 "primary": true 8 } 9 ], 10 "webhook_url": "https://your-server.com/webhook", 11 "webhook_secret": "your_webhook_secret", 12 "reference": "order-123" 13 }
Webhook Events
SplitRoute will send webhook notifications for the following events:
Event | Description |
---|---|
invoice.created | Invoice has been created |
invoice.paid | Payment has been received |
invoice.expired | Invoice has expired without payment |
invoice.forwarded | Funds have been forwarded (if using forward_account) |
invoice.done | Invoice processing is complete |
Webhook Payload
Webhook notifications are sent as HTTP POST requests with a JSON payload:
JSON
1 { 2 "event": "invoice.paid", 3 "data": { 4 "invoice_id": "inv_123abc", 5 "secret_id": "sec_xyz789", 6 "account_address": "nano_1abc...", 7 "required_amount": "0.123456", 8 "received_amount": "0.123456", 9 "nominal_amount": "10.00", 10 "nominal_currency": "USD", 11 "exchange_rate": "81.23", 12 "paid_at": "2023-01-01T00:30:00Z", 13 "reference": "order-123", 14 "user_data": "Optional user data" 15 }, 16 "timestamp": "2023-01-01T00:30:00Z" 17 }
Webhook Signature
To verify that webhook notifications are coming from SplitRoute, each request includes a signature in the X-Webhook-Signature
header. The signature is created using HMAC-SHA256 with your webhook secret:
X-Webhook-Signature: signature
X-Webhook-Timestamp: timestamp
To verify the signature:
HMAC-SHA256(webhook_secret, timestamp + request_body)
Here's an example of how to verify the signature in Node.js:
JAVASCRIPT
1 const crypto = require('crypto'); 2 3 function verifyWebhookSignature(req, secret) { 4 const signature = req.headers['x-webhook-signature']; 5 const timestamp = req.headers['x-webhook-timestamp']; 6 const body = JSON.stringify(req.body); 7 8 const expectedSignature = crypto 9 .createHmac('sha256', secret) 10 .update(timestamp + body) 11 .digest('hex'); 12 13 return crypto.timingSafeEqual( 14 Buffer.from(signature), 15 Buffer.from(expectedSignature) 16 ); 17 }
Python
1 import hmac 2 import json 3 4 def generate_webhook_signature(secret: str, timestamp: str, payload: dict) -> str: 5 """Generate a webhook signature using HMAC-SHA256""" 6 # Create compact JSON without extra whitespace 7 payload_str = json.dumps(payload, sort_keys=True, separators=(",", ":")) 8 9 # Combine timestamp and payload 10 signed_content = f"{timestamp}{payload_str}" 11 12 # Generate HMAC-SHA256 signature 13 signature = hmac.new( 14 secret.encode(), signed_content.encode(), hashlib.sha256 15 ).hexdigest() 16 17 return signature
Webhook Retries
If your webhook endpoint returns a non-2xx status code, SplitRoute will retry the webhook delivery with the following schedule:
- 1st retry: 5 seconds after the initial attempt
- 2nd retry: 30 seconds after the 1st retry
- 3rd retry: 2 minutes after the 2nd retry
- 4th retry: 5 minutes after the 3rd retry
- 5th retry: 10 minutes after the 4th retry
Best Practices
-
Respond quickly: Your webhook endpoint should respond with a 2xx status code as quickly as possible. Process the webhook asynchronously if needed.
-
Verify signatures: Always verify the webhook signature to ensure the request is coming from SplitRoute.
-
Handle idempotency: Webhook deliveries may be retried, so your endpoint should be idempotent (able to handle the same event multiple times without side effects).
-
Store raw payloads: Store the raw webhook payload for debugging and auditing purposes.
-
Implement proper error handling: Return appropriate HTTP status codes based on the success or failure of webhook processing.
Example Webhook Handler
Here's an example of a webhook handler in Node.js using Express:
JAVASCRIPT
1 const express = require('express'); 2 const crypto = require('crypto'); 3 const bodyParser = require('body-parser'); 4 5 const app = express(); 6 app.use(bodyParser.json()); 7 8 app.post('/webhook', (req, res) => { 9 const webhookSecret = 'your_webhook_secret'; 10 11 // Verify signature 12 const signature = req.headers['x-webhook-signature']; 13 const timestamp = req.headers['x-webhook-timestamp']; 14 15 if (!signature || !timestamp) { 16 return res.status(400).send('Missing signature or timestamp'); 17 } 18 19 const body = JSON.stringify(req.body); 20 const expectedSignature = crypto 21 .createHmac('sha256', webhookSecret) 22 .update(timestamp + body) 23 .digest('hex'); 24 25 if (signature !== expectedSignature) { 26 return res.status(401).send('Invalid signature'); 27 } 28 29 // Process the webhook 30 const event = req.body.event; 31 const data = req.body.data; 32 33 console.log(`Received webhook: ${event}`); 34 35 switch (event) { 36 case 'invoice.paid': 37 // Handle payment received 38 console.log(`Invoice ${data.invoice_id} has been paid`); 39 // Update order status, send confirmation email, etc. 40 break; 41 case 'invoice.expired': 42 // Handle expired invoice 43 console.log(`Invoice ${data.invoice_id} has expired`); 44 // Notify customer, cancel order, etc. 45 break; 46 // Handle other events... 47 } 48 49 // Acknowledge receipt of the webhook 50 res.status(200).send('Webhook received'); 51 }); 52 53 app.listen(3000, () => { 54 console.log('Webhook server listening on port 3000'); 55 });