Skip to main content
When you provide a webhook_url when creating an invoice, Kibble sends a HMAC-SHA256 signed POST request to that URL as soon as payment is detected on-chain. This lets you automate downstream actions — such as marking an order as fulfilled or sending a receipt — without polling the status API.

Setting a webhook URL

Include webhook_url in your POST /api/invoices request body:
cURL
curl -X POST https://pay.kibble.sh/api/invoices \
  -H "Content-Type: application/json" \
  -d '{
    "merchant_email": "you@example.com",
    "merchant_name": "Acme SaaS",
    "merchant_address": "123 Main St, SF",
    "vendor_email": "vendor@example.com",
    "vendor_name": "Vendor Co",
    "line_items": [
      { "description": "Consulting", "quantity": 10, "unit_price": "150.00" }
    ],
    "due_date": "2026-05-28",
    "wallet_type": "privy",
    "webhook_url": "https://example.com/webhooks/kibble"
  }'
The creation response will include a webhook_secret:
{
  "invoice_id": "a1b2c3d4-...",
  "invoice_number": "INV-0001",
  "invoice_url": "https://pay.kibble.sh/i/abc12345",
  "webhook_secret": "abc..."
}
Store webhook_secret securely (for example, as an environment variable). Kibble returns it only once, at invoice creation time. You need it to verify every incoming webhook payload.

Webhook payload

When payment is detected, Kibble sends a POST request to your webhook_url with the following JSON body:
{
  "event": "invoice.paid",
  "invoice_id": "a1b2c3d4-...",
  "invoice_number": "INV-0001",
  "total_amount": "1500.00",
  "status": "paid"
}
The status field reflects the final payment status and will be one of paid, partial, or excess.
Kibble fires the webhook exactly once, when the invoice transitions to paid, partial, or excess. It does not fire on intermediate events such as the vendor opening the invoice page.

Verifying the signature

Kibble signs every webhook payload with HMAC-SHA256 using your webhook_secret. The signature is included in the X-Kibble-Signature request header in the format sha256={hex_digest}.
Always verify the HMAC signature before processing a webhook payload. Without verification, your endpoint could be triggered by unauthorized requests from any source.
Verify the signature by computing the expected HMAC over the raw request body and comparing it to the value in the header:
import crypto from 'crypto';

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return `sha256=${expected}` === signature;
}

// Express example
app.post('/webhooks/kibble', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-kibble-signature'];
  const isValid = verifyWebhook(req.body, signature, process.env.KIBBLE_WEBHOOK_SECRET);

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

  const payload = JSON.parse(req.body);
  // Handle payload.event === 'invoice.paid'
  res.sendStatus(200);
});
Pass the raw request body bytes directly to the HMAC function — before any JSON parsing. Parsing and re-serializing the body can change whitespace or key ordering, which will cause signature verification to fail.