Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.usebila.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

Register a Webhook

curl --request POST \
  --url https://sandbox.usebila.com/api/v1/bila/webhooks \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: sk_test_your_api_key_here' \
  --data '{
    "url": "https://your-server.com/webhooks/bila",
    "events": [
      "payment.completed",
      "payment.failed",
      "withdrawal.completed",
      "withdrawal.failed",
      "order.paid"
    ]
  }'
The full signing secret is returned only at creation time. Store it immediately — it is masked on all subsequent calls. If lost, rotate the secret to get a new one.

Supported Events

EventWhen It Fires
payment.completedPayment finished — definitive final outcome
payment.failedPayment failed — definitive final outcome
payment.createdPayment initiated
withdrawal.createdPayout started
withdrawal.completedPayout succeeded
withdrawal.failedPayout failed
order.createdCustomer placed a store order
order.paidOrder paid
order.cancelledOrder cancelled
stock.lowProduct stock at or below threshold
settlement.completedSettlement completed
collection.completed and transfer.completed fire at initiation with status PROCESSING — not at final settlement. Always subscribe to payment.completed and payment.failed for the definitive outcome.

Delivery Headers

HeaderWhat It Contains
X-Bila-EventEvent name — e.g. payment.completed
X-Bila-DeliveryUnique delivery ID — use to deduplicate events
X-Bila-TimestampUnix timestamp used for signing
X-Bila-Signaturesha256=<hex-digest>
Bila retries up to 3 times on failure (~5s, ~10s, ~20s). Return HTTP 200 immediately and process asynchronously.

Verify Signatures

import * as crypto from 'crypto';

function verifyBilaWebhook(
  rawBody: string,
  timestampHeader: string,
  signatureHeader: string,
  secret: string,
  maxAgeSeconds = 300,
): boolean {
  const timestamp = Number(timestampHeader);
  if (!Number.isFinite(timestamp)) return false;

  const age = Math.abs(Math.floor(Date.now() / 1000) - timestamp);
  if (age > maxAgeSeconds) return false;

  const signedContent = `${timestamp}.${rawBody}`;
  const digest = crypto
    .createHmac('sha256', secret)
    .update(signedContent)
    .digest('hex');
  const expected = `sha256=${digest}`;

  const expectedBuf = Buffer.from(expected);
  const receivedBuf = Buffer.from(signatureHeader);
  if (expectedBuf.length !== receivedBuf.length) return false;

  return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}
Read the raw request body as a string before JSON parsing — the signature is computed against the raw bytes. Parsing first will break verification.

Pre-Launch Checklist

  • Webhook registered and signing secret stored
  • Signature verification implemented and tested
  • payment.completed and payment.failed received correctly
  • Duplicate delivery detection via X-Bila-Delivery working
  • Endpoint returns 200 in under 10 seconds