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:

EventDescription
invoice.createdInvoice has been created
invoice.paidPayment has been received
invoice.expiredInvoice has expired without payment
invoice.forwardedFunds have been forwarded (if using forward_account)
invoice.doneInvoice 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
1const crypto = require('crypto');
2
3function 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
1import hmac
2import json
3
4def 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:

  1. 1st retry: 5 seconds after the initial attempt
  2. 2nd retry: 30 seconds after the 1st retry
  3. 3rd retry: 2 minutes after the 2nd retry
  4. 4th retry: 5 minutes after the 3rd retry
  5. 5th retry: 10 minutes after the 4th retry

Best Practices

  1. Respond quickly: Your webhook endpoint should respond with a 2xx status code as quickly as possible. Process the webhook asynchronously if needed.

  2. Verify signatures: Always verify the webhook signature to ensure the request is coming from SplitRoute.

  3. Handle idempotency: Webhook deliveries may be retried, so your endpoint should be idempotent (able to handle the same event multiple times without side effects).

  4. Store raw payloads: Store the raw webhook payload for debugging and auditing purposes.

  5. 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
1const express = require('express');
2const crypto = require('crypto');
3const bodyParser = require('body-parser');
4
5const app = express();
6app.use(bodyParser.json());
7
8app.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
53app.listen(3000, () => {
54 console.log('Webhook server listening on port 3000');
55});