POST to a URL you control whenever something happens to a bill,
invoice, credit note, or match group. Instead of polling GET /v1/bills/{id} in a loop waiting
for extraction to finish, you get told the moment it does.
Most events carry the full entity snapshot — the same shape the corresponding GET endpoint
returns — so you usually don’t need a follow-up call. A few send a compact reference instead and
expect you to fetch the detail; see what each event sends.
How it works
1
Create a subscription
In the Dolfin portal, add your HTTPS endpoint and pick the
events you want. Copy the signing secret — it’s shown once.
2
Receive the POST
Dolfin delivers each matching event to your URL with a
Dolfin-Signature header.3
Verify the signature
Recompute the HMAC over the raw request body and compare. Reject anything that doesn’t match.
4
Return 2xx
Any
2xx acknowledges the event. Anything else — or no response within 10 seconds — is
retried with backoff.Create a subscription
Subscriptions are managed in the Dolfin portal under Webhooks. Create one with:- URL — must be
https://and publicly reachable. Private, loopback, link-local, and cloud metadata addresses are rejected. - Event types — one or more from the catalog. Subscribe only to what you handle; you can change the selection later.
whsec_…) and shows it once, at creation. Store it
somewhere your webhook handler can read it. If you lose it or need to cycle it, rotate the secret
from the portal — the previous one stops working immediately.
Subscriptions are client-wide. One subscription covers every organisation under your client,
so you don’t need one per organisation. Each event carries
organisationId — route on that
field. You can create up to 10 subscriptions per client, which is usually more than enough:
use separate ones to split traffic across services, not across organisations.The event envelope
Every delivery has the same outer shape. Onlydata changes between event types.
Two headers are also set, so you can route or de-duplicate without parsing the body:
Dolfin-Event-Id and Dolfin-Event-Type.
Example: react to bill extraction
This is the flow that most often replaces polling. Uploading a bill returns immediately — the document is read asynchronously — so you need to know when the extracted fields are ready. Subscribe to these four events:
Upload the bill as usual:
Extracting. Rather than polling it, wait
for the webhook:
confidence score between 0 and 1, so you can route
low-confidence documents to a human and let clean ones flow through.
Extraction methods. Dolfin can read bill documents with either OCR or LLM vision extraction.
Which one your account uses is configured per client — contact us
to switch. Either way the events, states, and extracted fields are identical, so nothing in your
integration changes.
The event is named
bill.ocr_failed while the bill state is ExtractionFailed. The event name
is kept as-is so existing integrations don’t break — treat it as “the document couldn’t be
read”, whichever extraction method your account uses.Verify the signature
Always verify before trusting a payload. Your endpoint is public, so anyone could post to it. Each request carries:t is the Unix timestamp of the delivery attempt and v1 is a hex HMAC-SHA256. To verify:
- Concatenate
t, a literal., and the raw request body. - Compute HMAC-SHA256 over that string, keyed with your signing secret.
- Compare against
v1in constant time. - Reject deliveries whose
tis outside your tolerance (5 minutes is a good default) to block replays.
Responding, retries, and delivery
Return any2xx to acknowledge. Do it quickly — Dolfin gives up on a delivery attempt after
10 seconds. If your processing is slow, enqueue the event and return 200 immediately.
A non-2xx response, a timeout, or a connection error schedules a retry:
After 10 failed attempts the delivery is marked failed and no longer retried automatically. You
can inspect deliveries and replay a failed one from the portal.
A few guarantees worth designing around:
- At-least-once delivery. A successful delivery can still be re-sent — for example if your
200was lost in transit. The eventidis stable across every attempt, so record processed IDs and ignore repeats. Make your handler idempotent. - No ordering guarantee. Events aren’t guaranteed to arrive in the order they occurred. Don’t
infer state from arrival order — trust the entity snapshot in
data, or re-fetch the entity. - Auto-disable. After 50 consecutive delivery failures, a subscription is switched off to stop hammering a dead endpoint. Re-enable it in the portal once you’ve fixed things; that resets the failure count.
Testing locally
Your endpoint has to be reachable over HTTPS, solocalhost won’t work directly. Expose your
local server with a tunnel:
What each event sends
data is not the same shape for every event. Most give you the whole entity; a couple give you
just enough to go and fetch it. Each event’s reference page shows its exact payload.
Full entity — no follow-up call needed. The nested object is identical to what the matching
GET endpoint returns.
Full entity plus context — the entity, plus the specifics of what just happened.
Summary or reference only — fetch the detail yourself if you need it.
Event catalog
Bills
Invoices
Three-way matching
See the three-way matching guide for what these mean in context.
Supplier credits
See the supplier credit notes guide for the full flow.
Companies
Next steps
Quick Start - AP
Upload a bill and drive it through review and approval.
Three-way Matching
Match purchase orders to bills and gate approval on variances.