Webhooks
Overview
Section titled “Overview”VisiBooks fires webhook events when resources are created or changed via the API. Configure webhook endpoints in Settings > Webhooks to receive these notifications.
Events
Section titled “Events”Receivables
Section titled “Receivables”| Event | Trigger |
|---|---|
books.contact.created | Contact created |
books.invoice.created | Invoice created |
books.invoice.posted | Invoice posted to ledger |
books.invoice.voided | Invoice voided |
books.invoice.paid | Invoice fully paid |
books.payment.created | Payment created |
books.payment.applied | Payment applied to invoice(s) |
books.payment.voided | Payment voided |
Payables
Section titled “Payables”| Event | Trigger |
|---|---|
books.bill.created | Bill created (direct or from commitment) |
books.bill.posted | Bill posted to ledger |
books.bill.voided | Bill voided |
books.bill.paid | Bill fully paid via vendor payment |
books.vendor_payment.created | Vendor payment recorded |
books.vendor_payment.applied | Payment applied to bill(s) |
books.vendor_payment.voided | Vendor payment voided |
books.commitment.matched | Invoice allocated to a commitment |
books.commitment.bill_created | Bill created from commitment allocations |
System
Section titled “System”| Event | Trigger |
|---|---|
books.reconciliation.completed | Reconciliation completed |
books.reconciliation.invalidated | Reconciliation invalidated |
books.period.closed | Accounting period closed |
books.period.reopened | Accounting period reopened |
Example Payloads
Section titled “Example Payloads”books.commitment.matched
Section titled “books.commitment.matched”Fired when an invoice is allocated to a commitment. Includes the commitment’s external IDs so your system can correlate.
{ "event": "books.commitment.matched", "event_id": "a1b2c3d4-...", "occurred_at": "2026-03-12T15:30:00Z", "data": { "commitment_id": 1, "commitment_number": "PO-2026-001", "external_source": "quarry", "external_id": "ORD-8834", "invoice_document_id": 7, "allocated_cents": 109500, "remaining_cents": 0, "status": "fully_matched" }}books.commitment.bill_created
Section titled “books.commitment.bill_created”Fired when a bill is created from approved commitment allocations.
{ "event": "books.commitment.bill_created", "event_id": "e5f6a7b8-...", "occurred_at": "2026-03-12T15:35:00Z", "data": { "bill_id": 12, "bill_total_cents": 124000, "invoice_document_id": 7, "commitment_ids": [1], "commitments": [ { "commitment_id": 1, "number": "PO-2026-001", "external_source": "quarry", "external_id": "ORD-8834", "status": "fully_matched" } ] }}books.bill.paid
Section titled “books.bill.paid”Fired when a bill’s balance reaches zero after a vendor payment is applied.
{ "event": "books.bill.paid", "event_id": "c9d0e1f2-...", "occurred_at": "2026-03-15T10:00:00Z", "data": { "bill_id": 12, "bill_number": "BILL-00042", "total_cents": 124000, "contact_id": 42 }}Headers
Section titled “Headers”Every webhook delivery includes these headers:
| Header | Description |
|---|---|
Content-Type | application/json |
User-Agent | VisiHub-Webhooks/1.0 |
X-VisiHub-Event | Event type (e.g., books.invoice.created) |
X-VisiHub-Signature | HMAC-SHA256 signature of the request body |
Payload
Section titled “Payload”Webhook payloads include the resource ID and external ID:
{ "event": "books.invoice.created", "data": { "invoice_id": 123, "external_id": "INV-001" }, "timestamp": "2026-03-12T15:30:00Z"}Signature Verification
Section titled “Signature Verification”Every webhook endpoint gets a unique secret (generated automatically when you create the endpoint). Deliveries are signed with HMAC-SHA256 using this secret.
The signature is sent in the X-VisiHub-Signature header as sha256=<hex digest>.
Verifying in your application
Section titled “Verifying in your application”# Pythonimport hmacimport hashlib
def verify_signature(payload_body: bytes, signature_header: str, secret: str) -> bool: expected = "sha256=" + hmac.new( secret.encode(), payload_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature_header)# Rubydef verify_signature(payload_body, signature_header, secret) expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, payload_body) Rack::Utils.secure_compare(expected, signature_header)endimport { createHmac, timingSafeEqual } from "crypto";
function verifySignature(payloadBody, signatureHeader, secret) { const expected = "sha256=" + createHmac("sha256", secret).update(payloadBody).digest("hex"); return timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));}Always use constant-time comparison to prevent timing attacks.
Retries and Failures
Section titled “Retries and Failures”Deliveries are retried up to 3 times with increasing backoff on failure. A delivery is considered successful if your endpoint returns a 2xx status code.
After 10 consecutive failures, the endpoint is automatically disabled. Re-enable it from Settings once the issue is resolved.
Testing
Section titled “Testing”Send a test ping to any webhook endpoint from the Settings page, or via the API:
curl -X POST https://api.visihub.app/api/webhook_endpoints/{id}/ping \ -H "Authorization: Bearer vk_..."