SettlraSettlra/Docs
DashboardAPI Playground

Webhooks

Settlra sends HTTP POST requests to your registered endpoints whenever a payout changes state. Webhooks are the recommended way to track payout progress — no polling required.

Registering an endpoint

Register your webhook URL in the Dashboard → Settings → Webhooks or via the API:

bash
"color:#ff7b72">curl "color:#79c0ff">-X POST https://api-sandbox.settlra.com/v1/webhooks/endpoints \
  "color:#79c0ff">-H "Authorization: Bearer sk_sandbox_your_key" \
  "color:#79c0ff">-H "Content-Type: application/json" \
  "color:#79c0ff">-d '{
    "url": "https://yourapp.com/webhooks/settlra",
    "events": ["payout.settled", "payout.failed", "payout.compliance_hold"],
    "description": "Production payout notifications"
  }'
json
{
  "id": "wh_01j3ab4cd5ef6gh7ij8kl9mn0p",
  "url": "https://yourapp.com/webhooks/settlra",
  "events": ["payout.settled", "payout.failed", "payout.compliance_hold"],
  "secret": "whsec_ABCDef123456...",
  "is_active": true,
  "created_at": "2024-07-01T12:00:00.000Z"
}

Store the secret securely — you'll use it to verify webhook signatures.

Event types

EventDescription
payout.createdA new payout has been created and is awaiting USDC.
payout.funds_receivedUSDC was confirmed on-chain. Processing begins.
payout.initiatedMobile money transfer has been sent to the provider.
payout.settledMobile money delivered to recipient. Final state.
payout.failedPayout failed after all retry attempts. Final state.
payout.compliance_holdCompliance flag raised; manual review required.
deposit.receivedUSDC deposit detected on the assigned address.
quote.expiredA quote expired without being used.

Webhook payload structure

json
{
  "id": "evt_01j3pq8rs9tu0vw1xy2za3bc4d",
  "type": "payout.settled",
  "created_at": "2024-07-01T12:04:35.000Z",
  "data": {
    "payout_id": "pyt_01j3pq8rs9tu0vw1xy2za3bc4d",
    "status": "SETTLED",
    "source_amount_usdc": 500,
    "target_amount_fiat": 1871250,
    "target_currency": "UGX",
    "exchange_rate": 3742.5,
    "recipient_phone": "+256700123456",
    "network": "MTN_UG",
    "provider_reference": "FLW-TXN-12345678",
    "settled_at": "2024-07-01T12:04:35.000Z"
  }
}

Signature verification

Every webhook request includes an X-Settlra-Signature header. This is an HMAC-SHA256 signature of the raw request body, signed with your webhook secret. Always verify the signature before processing the event.

Node.js verification

verify-webhook.jsjavascript
"color:#ff7b72">import crypto "color:#ff7b72">from 'crypto';
"color:#ff7b72">import express "color:#ff7b72">from 'express';

"color:#ff7b72">const app = express();

"color:#8b949e">// IMPORTANT: use raw body parser — JSON.parse changes byte content
app.use('/webhooks/settlra', express.raw({ "color:#ff7b72">type: 'application/json' }));

app.post('/webhooks/settlra', (req, res) => {
  "color:#ff7b72">const signature = req.headers['x-settlra-signature'];

  "color:#ff7b72">if (!signature) {
    "color:#ff7b72">return res.status(400).send('Missing signature header');
  }

  "color:#8b949e">// Compute expected signature
  "color:#ff7b72">const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', process.env.SETTLRA_WEBHOOK_SECRET)
    .update(req.body) "color:#8b949e">// req.body is Buffer when using raw parser
    .digest('hex');

  "color:#8b949e">// Use timingSafeEqual to prevent timing attacks
  "color:#ff7b72">const sigBuffer = Buffer."color:#ff7b72">from(signature);
  "color:#ff7b72">const expectedBuffer = Buffer."color:#ff7b72">from(expectedSignature);

  "color:#ff7b72">if (
    sigBuffer.length !== expectedBuffer.length ||
    !crypto.timingSafeEqual(sigBuffer, expectedBuffer)
  ) {
    "color:#ff7b72">return res.status(401).send('Invalid signature');
  }

  "color:#ff7b72">const event = JSON.parse(req.body.toString());

  switch (event."color:#ff7b72">type) {
    case 'payout.settled':
      "color:#ff7b72">await handlePayoutSettled(event.data);
      break;
    case 'payout.failed':
      "color:#ff7b72">await handlePayoutFailed(event.data);
      break;
    case 'payout.compliance_hold':
      "color:#ff7b72">await alertComplianceTeam(event.data);
      break;
  }

  "color:#8b949e">// Always "color:#ff7b72">return 200 quickly
  res.status(200).json({ received: "color:#79c0ff">true });
});

Python verification

verify_webhook.pypython
"color:#ff7b72">import hmac
"color:#ff7b72">import hashlib
"color:#ff7b72">import json
"color:#ff7b72">from flask "color:#ff7b72">import Flask, request, jsonify
"color:#ff7b72">import os

app = Flask(__name__)

@app.route('/webhooks/settlra', methods=['POST'])
"color:#ff7b72">def handle_webhook():
    signature = request.headers.get('X-Settlra-Signature', '')
    raw_body = request.get_data()  "color:#8b949e"># raw bytes

    "color:#8b949e"># Compute expected signature
    secret = os.environ['SETTLRA_WEBHOOK_SECRET'].encode()
    expected = 'sha256=' + hmac.new(
        secret, raw_body, hashlib.sha256
    ).hexdigest()

    "color:#8b949e"># Constant-time comparison
    "color:#ff7b72">if not hmac.compare_digest(signature, expected):
        "color:#ff7b72">return jsonify({'error': 'Invalid signature'}), 401

    event = json.loads(raw_body)

    "color:#ff7b72">if event['type'] == 'payout.settled':
        handle_payout_settled(event['data'])
    "color:#ff7b72">elif event['type'] == 'payout.failed':
        handle_payout_failed(event['data'])

    "color:#ff7b72">return jsonify({'received': "color:#79c0ff">True}), 200

Retry behavior

If your endpoint doesn't return HTTP 200299 within 10 seconds, Settlra retries with exponential backoff:

AttemptDelay after previous attempt
1st retry30 seconds
2nd retry5 minutes
3rd retry30 minutes
Final (4th)2 hours

After 5 total failed delivery attempts, the webhook delivery is marked as failed. You can resend specific events from the dashboard.

Webhook endpoint management API

MethodPathDescription
GET/v1/webhooks/endpointsList your webhook endpoints
POST/v1/webhooks/endpointsRegister a new endpoint
GET/v1/webhooks/endpoints/:idGet one endpoint
PUT/v1/webhooks/endpoints/:idUpdate URL or events
DELETE/v1/webhooks/endpoints/:idDelete an endpoint
POST/v1/webhooks/endpoints/:id/testSend a test event

Best practices

  • Respond quickly. Return 200immediately and process the event asynchronously (e.g., in a queue). Settlra times out after 10 seconds.
  • Handle duplicates. Webhooks may be delivered more than once. Make your handler idempotent — check the id field.
  • Use HTTPS. Production endpoints must use valid TLS. Self-signed certs are rejected.
  • Don't trust the payload alone. Always verify the signature. An attacker can POST arbitrary payloads to your webhook URL.