WEBHOOKS · INTEGRATION
Webhooks and event-based integration: HMAC, idempotency, retry
Webhooks replace polling with push events. HMAC signature, replay protection, idempotency keys. May 2026 best practices for robust pipelines.
Researched & fact-checked by: DuneDive LLC · As of: 2026-05
What are webhooks?
A webhook is an event-based HTTP interface: a source system (sender) calls a pre-configured URL at the receiver system as soon as a specific event occurs. Instead of the receiver polling regularly, it is notified actively (push). The payload is typically JSON, the transport HTTPS POST.
A classic example from the Swiss fiduciary world: Stripe sends a payment_intent.succeeded webhook as soon as a client has successfully completed an online payment. Instead of your system querying Stripe every 5 minutes whether new payments exist, you receive the information within seconds. A second example: Bexio sends a webhook invoice.paid as soon as a client invoice is marked as paid. Your system can immediately trigger a thank-you email or a posting in payroll.
Webhooks have three central difficulties: authentication, idempotency, and delivery guarantees. First: who sent the webhook? If the URL is known, someone could forge malicious requests. Solution: HMAC signatures. The sender computes a hash from payload + secret shared secret and sends it in the X-Signature header. The receiver computes the same hash and compares.
Second: idempotency. Webhooks can be sent multiple times, e.g. on retry after timeout. If your receiver processes a webhook twice, that can mean a double posting or a double mail. Solution: idempotency keys. The sender sends a unique event-ID header, the receiver stores already-processed IDs in a DB and ignores duplicates.
Third: delivery guarantees. Webhooks can be lost (network problems, receiver outage). Robust senders implement exponential backoff (1s, 2s, 4s, 8s, ..., up to 24h) and collect undeliverable events in a dead-letter queue. Robust receivers should answer all webhooks within 30 seconds with a 2xx; otherwise the webhook is considered failed.
Why it matters for Swiss fiduciary
An average fiduciary setup has 8 to 15 integrated systems: Bexio or Abacus, banking (CAMT.053 import), Stripe, Microsoft 365 or Google Workspace, a client CRM, a time tracker, an AI layer. If all these systems are integrated via polling, thousands of polling calls happen per day, 99 percent of which deliver no new data.
Webhooks solve the problem on three levels. First: speed. A client pays his invoice at 14:23, the confirmation mail goes out at 14:23:02, instead of only in the next 5-minute polling run. Second: API costs. With Bexio you are below the 50 requests/second rate limit. Without polling you save 70-95 percent of API calls. Third: scalability. As client count grows, polling effort grows linearly. Webhooks scale by themselves: no event, no call.
For AI workflows webhooks are especially valuable. A new mail arrives in the inbox: Microsoft Graph sends a change notification, the AI triage starts immediately, the case handler sees the prioritised mail in under 30 seconds. A new document is uploaded to SharePoint: the webhook triggers the OCR pipeline and the document is in the RAG index within 2 minutes.
How it works
A production-ready webhook pipeline has four building blocks: endpoint, signature verification, idempotency store, processing queue.
The endpoint is an HTTPS URL that accepts POST requests. Important: respond quickly (under 30 seconds) with 2xx. The actual processing (AI call, database insert) happens asynchronously in a queue.
Signature verification: read the signature header (e.g. Stripe-Signature, X-Bexio-Signature) and compute the HMAC-SHA256 over the raw request body with the shared secret. Compare in constant time to avoid timing attacks. Example in Node.js:
```javascript const crypto = require('crypto'); function verify(rawBody, signatureHeader, secret) { const expected = crypto.createHmac('sha256', secret) .update(rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signatureHeader) ); } ```
Idempotency store: read the event-ID header and check a webhook_events table. If the ID already exists, respond immediately with 200 OK and ignore the event. Otherwise store the ID with a timestamp and continue processing.
Processing queue: instead of processing the webhook synchronously, write the event to a queue (Redis, RabbitMQ, AWS SQS, or a simple Postgres-with-NOTIFY setup) and respond to the sender with 200 OK. A worker process picks events from the queue and processes them. Advantages: sender gets a fast response, you can retry processing failures, you can scale load independently of webhook volume.
Best practices May 2026: HTTPS with a valid certificate (no self-signed), webhooks only over defined paths (not /), logs of all incoming events for debugging, dead-letter queue for unprocessable events, a monitoring dashboard for input rate and error rate.
Webhook setup in 5 steps
- 01Define the webhook URL (e.g. https://api.firm.ch/webhooks/bexio), ensure HTTPS certificate, do not let the path be guessed.
- 02Configure the shared secret at the sender, store it in a secrets manager at the receiver, never commit it to code.
- 03Implement the endpoint: read the raw body, verify the HMAC signature, look up the idempotency key in the DB, write to the queue, respond with 200 OK.
- 04Set up the worker process for processing: read from the queue, business logic (AI call, Bexio update), put in DLQ on error.
- 05Set up monitoring: incoming events per hour, error rate, DLQ depth, alarm on anomalies.
When to use webhooks
Webhooks are the right choice when (a) the source service offers them, (b) events should be processed in a timely manner, and (c) polling effort is noticeable.
Concrete use-cases in Swiss fiduciary: Bexio webhook invoice.paid triggers a thank-you mail and payroll insert. Stripe webhook payment_intent.succeeded triggers client confirmation and Bexio insert. Microsoft Graph change notification for new mails triggers AI triage. SharePoint webhook for new documents triggers OCR and RAG indexing. Sage 50 webhook bank-transaction-created triggers an AI booking suggestion.
Rule of thumb: if you query an API more than 4 times per hour, switching to webhooks is worthwhile. If you query an API once every few days, polling is often simpler.
When not to use
If the source service offers no webhooks, you must poll. Example: old SOAP APIs without a webhook layer (Abacus pre-2025.2, Sage pre-2026, SAP B1 without add-on).
If processing latency is uncritical (such as quarterly reports), a webhook setup is over-engineered. A nightly cron job with API polling is cheaper and simpler here.
If the receiver is not publicly reachable (behind a firewall, no reverse tunnel), you cannot receive webhooks. Solutions: Cloudflare Tunnel, ngrok for tests, or a webhook relay service.
For mandates with very sensitive data (such as professional-secrecy mandates), the webhook endpoint must be additionally secured: IP whitelisting of the sender, mTLS (mutual TLS) instead of just HTTPS, a separate network segment for the receiver.
Trade-offs
STRENGTHS
- Real-time processing instead of polling latency
- Saves 70-95 percent of API calls for active services
- Scales automatically with event volume, no loop logic
- Clear separation: sender notifies, receiver processes
WEAKNESSES
- Endpoint must be publicly reachable, firewall configuration required
- Robustness requires HMAC, idempotency, and queue, setup effort higher than polling
- Webhook loss possible, build reconciliation polling as fallback
- Subscriptions at Graph and Google must be renewed, custom management required
FAQ
Which services offer webhooks?
As of May 2026: Bexio (comprehensive), Stripe (market standard), Microsoft Graph (change notifications, max 3-day subscription), Google Workspace (Drive Push API, max 24h), SharePoint Online (via Graph), Sage 50 (since 2026 GA), Slack, GitHub, GitLab, n8n. Abacus since 2025.2. SAP B1 only via add-ons.
How do I debug webhooks?
Log every incoming request including headers and body. Tools like webhook.site or Beeceptor allow inspecting requests in the cloud. For local debugging ngrok or Cloudflare Tunnel are indispensable. Stripe and Bexio offer a replay button in their dashboard to resend events.
What is a dead-letter queue?
A DLQ is a second queue into which events land that could not be successfully processed after multiple retries (typically 5-10). They prevent the main queue from clogging and give you a pool for manual inspection. The DLQ should have monitoring; a growing DLQ depth is a bug indicator.
How do I further secure webhooks?
Three levels: (1) always verify the HMAC signature. (2) IP whitelisting of the sender if the sender IP is stable (e.g. Stripe publishes IP ranges). (3) mTLS for highly sensitive setups: client and server certificate required for the HTTP handshake. Additionally: do not let the webhook URL be guessed (random token in path), replay protection via timestamps (reject events older than 5 minutes).