Prerequisites
Confirm the following before registering a webhook:- An HTTPS endpoint you can deploy code to. It must be publicly reachable from DualEntry’s egress and terminate TLS. Loopback and private IPs are rejected on registration for SSRF protection. If your infrastructure allowlists inbound traffic, permit DualEntry’s source IPs.
- A DualEntry organization-scoped Public API key with the
INTEGRATIONpermission (create,view,edit,archiveas appropriate). Create the key under Organization Management → your organization → API Keys. See Authentication. - A place to store the signing secret returned by the register call. Treat it like a password. It is shown once and can never be retrieved again.
- Agreement with engineering on which topics you need. DualEntry maintains an explicit allowlist of topics for business records (customers, invoices, bills, journal entries, and similar). Not every audited change is exposed.
Source IPs to allowlist
DualEntry sends webhook events from the production IPs listed in the table below. DualEntry sends events from either IP, so allow both at your firewall, WAF, or reverse proxy. Do not pin traffic to just one address.
Use this list in addition to (not as a replacement for) HMAC verification. IP allowlisting keeps casual traffic off the receiver; the signature check is what proves a request actually came from DualEntry.
How DualEntry webhooks work
DualEntry webhooks deliver events via HTTPSPOST with these guarantees: signed payloads, at-least-once delivery, unordered arrival, and status-aware retries. Read this once; every subsequent decision in this doc depends on it.
Your receiver must be idempotent because the system guarantees at-least-once delivery and does not order events.
Register a webhook
All calls authenticate with an organization API key via theX-API-KEY header.
1
List the topics you can subscribe to
The subscribable topic catalog is an allowlist of business records with Topic names follow the pattern
created, updated, and deleted lifecycle actions.<app>.<Model>/<action>, for example core.invoice/updated. Anything not returned by this endpoint cannot be subscribed to and will be rejected with 422 on registration.2
Register your endpoint
Specify a public HTTPS URL and select the topics you want to subscribe to. The response includes a signing
secret exactly once, so copy it into your secrets manager immediately.Verify and inspect deliveries
After registration, confirm the wiring is real end-to-end and learn where to look when something goes wrong.-
Verify a live delivery. Trigger any subscribed record change (create a customer, edit an invoice) and confirm your endpoint received a
POSTwhose signature verifies against the stored secret. See Verify the HMAC signature below for the algorithm. -
Inspect the delivery log. Every attempt (successful or failed) is recorded. Poll the delivery log by webhook UUID:
Each entry shows
event_id,topic,status,attempts,response_status,last_error,delivered_at, andcreated_at. This is the single source of truth when a downstream system claims it “didn’t get” an event.
Payload envelope
Every request body follows the same shape, so your receiver can dispatch ontopic without special-casing per resource.
event_idis a deterministic UUID. DualEntry generates the sameevent_idfor the same business event across retries, replays, and process restarts, so use it as your idempotency key.objectis a stable reference ({"id": ...}), not the full record. Fetch the object from the corresponding/public/v2/<resource>/{id}/endpoint if you need current field values. This keeps the payload compact and avoids shipping stale field snapshots.api_versionpins the envelope schema for that delivery. Rows in flight during version updates continue to ship with the version they were created under, so key offapi_versionif you support more than one.
Verify the HMAC signature
This section covers what to check on every incoming request so you can trust the payload. Read the headers, run three verification steps, and only then dispatch on the event.Authentication headers
Every delivery carries three headers you use to authenticate the request:Verification steps
Verification is three things, in this order, and none of them is optional:- Recompute the HMAC over the exact string
"<timestamp>.<raw_body>"using your storedsecret. Use the raw bytes of the request body. Do not parse JSON and re-serialize, or whitespace and key order will change the hash. - Constant-time compare the recomputed digest against the
Dualentry-Webhook-Signature-V1header value. A regular string comparison reveals timing information about prefix matches, enabling timing attacks. - Reject stale timestamps. If
abs(now - timestamp) > 300seconds, reject the request. This defeats attackers who capture an old signed request and replay it later.
Reference receiver implementation
This section provides ready-to-adapt code for the three verification steps above. The Python snippet is a full production-shaped receiver; the Node.js snippet is a signature-verification helper you can wire into any Express handler.Python / Flask
The snippet below is a minimal, production-shaped receiver. It verifies the signature, rejects replays, dedupes onevent_id, and returns 200 fast. Load WEBHOOK_SECRET from your secrets store; never hardcode it.
- Signature verification blocks anything that isn’t signed with your secret.
- The 5-minute replay window blocks captured-and-replayed old requests.
- The
event_iddedupe handles at-least-once delivery. - A fast
2xxkeeps you out of the retry loop; do the actual work asynchronously.
Node.js / Express
The algorithm is the same everywhere: HMAC-SHA256 over"<timestamp>.<raw_body>", hex-encoded, constant-time compared.
Manage webhooks over the API
Once a webhook is live, you rarely re-register it. Instead, you list what’s active, patch the URL when your receiver moves, toggleis_active off during maintenance windows, and pull the delivery log when downstream reports a missing event. The full lifecycle runs on the same /public/v2/webhooks/ router. All routes are org-scoped by the API key and gated by the INTEGRATION permission.
Full request and response schemas are in the API reference.
Retry, disable, and reactivation policy
WebhookDelivery owns the retry state machine. Your endpoint’s response determines DualEntry’s next action:
If an endpoint fails continuously for 5 days it is automatically disabled. Reactivate it after fixing the receiver:
GET /public/v2/invoices/).
Operate safely in production
The rules below cover most of the incidents this system creates. Treat them as a pre-launch checklist for the receiver.Troubleshoot common failures
Common webhook issues and their solutions.FAQ
Common questions about webhook implementation and behavior.Are webhooks the same as the Stripe / Brex / Plaid webhooks I see in DualEntry?
No. Those are inbound webhooks that DualEntry receives from third parties (payment processors, banking) to update your books. This page describes outbound webhooks that DualEntry sends to systems you own when your records change. They share the word “webhook” and nothing else.Do webhooks include the full record body?
No. The payload hasobject.id and enough envelope to route on (topic, object_type). Fetch the full record from the matching /public/v2/<resource>/{id}/ endpoint when you need current field values. This keeps payloads small, avoids shipping stale snapshots, and forces receivers to work against the API’s canonical representation.
Can I subscribe to every change in the system?
No. DualEntry maintains an explicit allowlist of business records as the topic catalog. Internal bookkeeping, session events, and non-lifecycle actions are never exposed. If you need a record that isn’t in the catalog, ask engineering to add it; it is a small edit on their side.Can I use webhooks and polling together?
Yes, and for critical flows you probably should. Webhooks are the fast path; a periodic reconciliation job that lists the same resource on a longer schedule catches anything the webhook missed (an endpoint outage, an accidentally deleted webhook). Idempotency onevent_id and record ID keeps them from stepping on each other.
Next steps
- Register your first webhook against a non-production DualEntry organization using the Register endpoint.
- Read the Public API v2 webhooks reference for exact request and response schemas.
- Compare webhook-driven flows to polling in Building a custom integration.
- Return to Integrations for the full list of prebuilt connectors.
