Skip to main content
Webhooks allow your application to receive real-time notifications when events occur in Bousol. Instead of polling the API, webhooks push data to your server automatically.

Overview

When an event occurs (e.g., invoice paid), Bousol sends an HTTP POST request to your configured endpoint with event details.
Event Occurs → Bousol → POST to your endpoint → Your Server

Setting Up Webhooks

Via Dashboard

  1. Go to DevelopersWebhooks
  2. Click Add Endpoint
  3. Configure the endpoint:
FieldDescription
URLYour server endpoint (must be HTTPS)
EventsWhich events to receive
DescriptionOptional description
  1. Click Create
  2. Copy the Webhook Secret for signature verification

Via API

curl -X POST https://api.bousol.app/mfi/webhook-endpoints \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/bousol",
    "events": ["invoice.paid", "payout.completed"]
  }'

Event Types

Invoice Events

EventDescription
invoice.createdInvoice was created
invoice.sentInvoice was sent to customer
invoice.viewedCustomer viewed the invoice
invoice.paidInvoice was paid
invoice.overdueInvoice passed due date
invoice.voidedInvoice was voided
EventDescription
payment_link.createdPayment link was created
payment_link.paidPayment was made via link

Payout Events

EventDescription
payout.createdPayout was created
payout.approvedPayout was approved
payout.rejectedPayout was rejected
payout.completedPayout was sent successfully
payout.failedPayout execution failed

Disbursement Events

EventDescription
disbursement.createdDisbursement was created
disbursement.submittedSubmitted for approval
disbursement.approvedApproved for execution
disbursement.rejectedRejected
disbursement.completedAll payments sent
disbursement.failedExecution failed

Client Events

EventDescription
client.createdClient was created
client.updatedClient was updated
client.deletedClient was deleted

Webhook Payload

Structure

{
  "id": "evt_xxxxxxxx",
  "type": "invoice.paid",
  "created": "2026-01-25T10:30:00Z",
  "data": {
    "id": "inv_xxxxxxxx",
    "invoiceNumber": "INV-001",
    "clientId": "cli_xxxxxxxx",
    "amount": "500.00",
    "currency": "HTG",
    "status": "paid",
    "paidAt": "2026-01-25T10:30:00Z",
    "txHash": "abc123..."
  }
}

Fields

FieldDescription
idUnique event ID
typeEvent type
createdEvent timestamp (ISO 8601)
dataEvent-specific data

Verifying Webhooks

Verify webhook signatures to ensure requests come from Bousol.

Signature Header

Each webhook includes a signature header:
X-Bousol-Signature: t=1706180400,v1=abc123...

Verification Steps

  1. Extract timestamp (t) and signature (v1) from header
  2. Construct the signed payload: {timestamp}.{request_body}
  3. Compute HMAC-SHA256 with your webhook secret
  4. Compare computed signature with v1

Node.js Example

const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const [timestamp, sig] = signature.split(',').reduce((acc, part) => {
    const [key, value] = part.split('=');
    acc[key] = value;
    return acc;
  }, {});

  const signedPayload = `${timestamp}.${payload}`;
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(sig),
    Buffer.from(expectedSig)
  );
}

// In your webhook handler
app.post('/webhooks/bousol', (req, res) => {
  const signature = req.headers['x-bousol-signature'];
  const isValid = verifyWebhook(
    JSON.stringify(req.body),
    signature,
    process.env.BOUSOL_WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  // Process the webhook
  const event = req.body;
  console.log(`Received ${event.type}:`, event.data);

  res.status(200).send('OK');
});

Python Example

import hmac
import hashlib

def verify_webhook(payload, signature, secret):
    parts = dict(p.split('=') for p in signature.split(','))
    timestamp = parts['t']
    sig = parts['v1']

    signed_payload = f"{timestamp}.{payload}"
    expected_sig = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(sig, expected_sig)

Responding to Webhooks

Success Response

Return a 2xx status code to acknowledge receipt:
HTTP/1.1 200 OK

Failure Handling

If your endpoint returns a non-2xx status or times out, Bousol retries:
AttemptDelay
1Immediate
25 minutes
330 minutes
42 hours
524 hours
After 5 failed attempts, the webhook is marked as failed.

Timeouts

Your endpoint must respond within 30 seconds. For long-running tasks:
  1. Acknowledge the webhook immediately (return 200)
  2. Process the event asynchronously

Managing Webhooks

Viewing Endpoints

Go to DevelopersWebhooks to see all endpoints:
ColumnDescription
URLEndpoint URL
EventsSubscribed events
StatusActive, Paused, or Failing
Last DeliveryMost recent attempt

Viewing Deliveries

Click on an endpoint to see delivery history:
ColumnDescription
EventEvent type and ID
StatusSuccess, Failed, Pending
ResponseHTTP status code
TimeDelivery timestamp

Resending Events

To resend a failed event:
  1. Go to the endpoint’s delivery history
  2. Find the failed delivery
  3. Click Resend

Disabling Endpoints

To temporarily disable an endpoint:
  1. Go to DevelopersWebhooks
  2. Click on the endpoint
  3. Click Pause
Paused endpoints don’t receive events. Events are not queued during pause.

Testing Webhooks

Test Events

Send test events to your endpoint:
  1. Go to DevelopersWebhooks
  2. Click on the endpoint
  3. Click Send Test Event
  4. Select an event type
  5. Click Send

Local Development

For local development, use a tunnel service:
  1. Install ngrok or similar
  2. Run ngrok http 3000
  3. Use the ngrok URL as your webhook endpoint
  4. Test with real or simulated events

Best Practices

Idempotency

Events may be delivered more than once. Make your handler idempotent:
app.post('/webhooks/bousol', async (req, res) => {
  const event = req.body;

  // Check if already processed
  const existing = await db.events.findById(event.id);
  if (existing) {
    return res.status(200).send('Already processed');
  }

  // Process event
  await processEvent(event);

  // Mark as processed
  await db.events.insert({ id: event.id, processedAt: new Date() });

  res.status(200).send('OK');
});

Event Ordering

Events may arrive out of order. Use timestamps and status checks rather than assuming order.

Security

  • Always verify signatures
  • Use HTTPS endpoints only
  • Validate event data before processing
  • Keep webhook secrets secure

Monitoring

  • Log all received webhooks
  • Alert on repeated failures
  • Monitor endpoint health
  • Review delivery history regularly

Next Steps