Webhooks Events HMAC

Webhooks guide

Get real-time notifications when invoice status changes in SPV. No polling required.

Event types

invoice.created
Invoice created in Billyou. Not yet sent to SPV.
invoice.sent_spv
Invoice submitted to ANAF SPV. Awaiting processing.
invoice.confirmed
ANAF confirmed the invoice. spvId is now set.
invoice.rejected
ANAF rejected the invoice. errors array contains reason.
invoice.voided
Invoice was voided/cancelled before SPV submission.

Payload structure

All webhook payloads share the same top-level structure. The data object varies by event type.

webhook-payload.json
{
  "id": "wh_01HX9...",
  "event": "invoice.confirmed",
  "created": 1712345678,
  "data": {
    "invoiceId": "inv_abc123",
    "spvId": "5000012345",
    "spvStatus": "ok",
    "errors": []
  }
}

Signature verification

Every webhook request includes an x-billyou-signature header. This is an HMAC-SHA256 of the raw request body, signed with your webhook secret. Always verify this before processing the payload.

webhook-handler.ts
import { createHmac } from 'crypto';
import Billyou from 'billyou-sdk';

// Express.js webhook handler
app.post('/webhooks/billyou', async (req, res) => {
  const signature = req.headers['x-billyou-signature'];
  const payload = JSON.stringify(req.body);

  // Verify signature
  const expected = createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(payload).digest('hex');

  if (signature !== expected) {
    return res.status(401).end();
  }

  const { event, data } = req.body;

  switch (event) {
    case 'invoice.confirmed':
      await markInvoicePaid(data.invoiceId);
      break;
    case 'invoice.rejected':
      await notifyUserOfError(data.errors);
      break;
  }

  res.status(200).json({ received: true });
});

Retry policy

If your endpoint returns a non-2xx status code or times out, Billyou retries the webhook with exponential backoff. Your endpoint must respond within 10 seconds.

5
Max retries
10s
Timeout per attempt
48h
Total retry window

Next steps