Skip to main content

Reference

Webhooks

Receive HTTP callbacks when things happen in Finora Business. Signed, retried, and scoped per event type.

Webhooks let your integration react to events in Finora Business without polling. Register an endpoint, subscribe to the events you care about, and we'll POST a signed JSON payload when each event fires.

Supported events

EventFires when
invoice.createdNew invoice is created (any source).
invoice.paidInvoice transitions to paid (full payment recorded).
invoice.voidedInvoice is voided, reversing its GL entry.
receipt.createdNew receipt is recorded.
expense.createdNew expense is posted.
bill.createdNew supplier bill is recorded.
bill.paidBill is fully paid.
payment.receivedA payment webhook from Paystack (or other gateway) lands successfully.
customer.createdNew customer is created.
customer.updatedCustomer record is updated.
report.generatedA scheduled report PDF/Excel is generated.
loan.disbursedA loan disbursement is recorded (enterprise).
loan.repaymentA loan repayment is recorded (enterprise).
health_score.changedBusiness health score crosses a threshold (enterprise).

Register a webhook

POST/v1/webhooks

Creates a webhook. The response includes a signing secret — save it, it's shown once.

Body

FieldTypeRequiredNotes
urlstringyesYour receiver endpoint. Must be HTTPS.
eventsarrayyesOne or more event types. At least one required.
descriptionstringnoShown in the dashboard.

Response — 201

{
  "success": true,
  "data": {
    "id": "wh_abc",
    "url": "https://your-app.com/finora-webhook",
    "events": ["invoice.paid", "receipt.created"],
    "secret": "whsec_a1b2c3d4e5f6...",
    "isActive": true,
    "createdAt": "2026-04-17T10:30:00.000Z"
  }
}

List webhooks

GET/v1/webhooks

Returns your registered webhooks (secrets are masked).

Update a webhook

PUT/v1/webhooks/{id}

Edit URL, events, or active status. The secret stays the same.

Delete a webhook

DELETE/v1/webhooks/{id}

Permanently removes the webhook. In-flight events are abandoned (no retries).

Verifying signatures

Every webhook POST includes X-Finora-Signature: sha256=<hex> computed as HMAC-SHA256 of the raw request body with your webhook secret as the key.

Node.js example

import crypto from 'node:crypto'
 
function verify(rawBody: string, signatureHeader: string, secret: string) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')
  const provided = signatureHeader.replace(/^sha256=/, '')
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(provided, 'hex'),
  )
}

Reject any request that fails verification — the webhook did not come from Finora Business.

Delivery semantics

  • Webhooks are delivered with at-least-once semantics. Your handler must be idempotent (we include an event ID in every payload).
  • Finora Business retries on 5xx responses with exponential backoff: 1m, 5m, 30m, 2h, 12h.
  • A 2xx response (any of 200–299) is considered acknowledged.
  • 4xx responses are not retried — fix the handler and update the webhook.

Payload shape

{
  "eventId": "evt_01HZXA...",
  "event": "invoice.paid",
  "businessId": "biz_001",
  "occurredAt": "2026-04-17T10:30:00.000Z",
  "data": {
    "invoiceId": "inv_abc",
    "invoiceNumber": "INV-2026-001",
    "totalKobo": 53750000,
    "amountPaidKobo": 53750000
  }
}

Limits

  • Maximum 20 webhooks per account.
  • URL must be HTTPS.
  • Each event is delivered within a few seconds under normal load.

Related endpoints

  • Invoices — source of invoice.* events
  • Receipts — source of receipt.created and payment.received

Reference index

Back to all modules