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}

And here's how to verify the signature in Python:

Python
1import hmac
2import hashlib
3import json
4from datetime import datetime
5
6def verify_webhook_signature(request, webhook_secret):
7 """
8 Verify a webhook signature from SplitRoute
9
10 Args:
11 request: The incoming request object with headers and body
12 webhook_secret: Your webhook secret string
13
14 Returns:
15 bool: True if signature is valid, False otherwise
16 """
17 signature = request.headers.get('X-Webhook-Signature')
18 timestamp = request.headers.get('X-Webhook-Timestamp')
19
20 if not signature or not timestamp:
21 return False
22
23 # Create compact JSON without extra whitespace
24 if isinstance(request.body, dict):
25 body_str = json.dumps(request.body, separators=(',', ':'), sort_keys=True)
26 else:
27 body_str = request.body # For frameworks that provide the raw body as string
28
29 # Combine timestamp and payload
30 signed_content = f"{timestamp}{body_str}"
31
32 # Generate HMAC-SHA256 signature
33 expected_signature = hmac.new(
34 webhook_secret.encode(),
35 signed_content.encode(),
36 hashlib.sha256
37 ).hexdigest()
38
39 # Use constant-time comparison to prevent timing attacks
40 return hmac.compare_digest(signature, expected_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});