Quick answer
A webhook is an HTTP callback sent when an event happens. Instead of a client repeatedly polling an API, the provider sends an event to a URL controlled by the client.
Webhooks are common for payments, subscriptions, repository events, emails, notifications, and workflow automation.
Polling vs webhooks
Polling asks the server for updates repeatedly:
client -> any new events?
client -> any new events?
client -> any new events?
Webhooks push events when they happen:
provider -> POST /webhooks/payment-succeeded
Webhooks reduce unnecessary polling, but they introduce delivery, security, retry, ordering, and idempotency concerns.
Basic webhook shape
A webhook request is usually a POST with a JSON body:
POST /webhooks/billing
Content-Type: application/json
X-Webhook-Event: invoice.paid
X-Webhook-Id: evt_123
{
"id": "evt_123",
"type": "invoice.paid",
"createdAt": "2026-06-29T12:00:00Z",
"data": {
"invoiceId": "inv_456",
"accountId": "acct_42"
}
}
The receiver should respond quickly, often after validating and storing the event for background processing.
Signature verification
Webhook endpoints are public URLs, so the receiver must verify that the event really came from the expected provider.
A common pattern is an HMAC signature header:
X-Webhook-Signature: sha256=...
The receiver computes a signature from the raw request body and shared secret, then compares it with the header.
Important details:
- Verify the raw body, not a reserialized JSON object.
- Use constant-time comparison when available.
- Include timestamp checks to reduce replay risk.
- Keep webhook secrets out of frontend code.
For a deeper verification workflow, read Webhook Signature Verification Explained.
Idempotency
Webhook delivery is often at-least-once. The same event may arrive more than once.
Receivers should store event IDs and ignore duplicates:
INSERT INTO processed_webhook_events (event_id, processed_at)
VALUES (?, CURRENT_TIMESTAMP);
If the insert fails because the event ID already exists, the receiver can safely return success without doing the side effect again.
Read Idempotency in APIs Explained for the broader duplicate-safe pattern.
Retries
Providers retry webhook delivery when the receiver returns an error or times out.
That means the receiver should:
- Return
2xxonly after the event is safely accepted. - Return errors for invalid signatures.
- Avoid slow processing in the request path.
- Use a queue for expensive work.
- Make downstream processing idempotent.
Read Message Queues Explained for background processing patterns.
Ordering
Do not assume webhooks always arrive in order.
For example:
customer.updated version 3
customer.updated version 2
Network delays, retries, and provider internals can reorder events. Use timestamps, versions, sequence numbers, or fetch-latest APIs when ordering matters.
If the event contains only a resource ID, the receiver can fetch the current state from the provider before applying changes.
Webhook response codes
Use response codes intentionally:
| Scenario | Response |
|---|---|
| Event accepted | 200 OK or 204 No Content |
| Invalid signature | 401 Unauthorized or 403 Forbidden |
| Unknown event type but valid request | Often 200 OK after logging, depending on provider expectations |
| Temporary internal failure | 500 or 503 to trigger retry |
Read REST API Status Codes Explained for broader status code guidance.
Receiver workflow
A reliable webhook receiver usually keeps the request path short:
receive request -> verify signature -> store event -> enqueue processing -> return 2xx
This pattern gives the provider a quick response while preserving the event for internal processing. The stored event should include the provider event ID, event type, received timestamp, signature verification result, processing status, and a safe copy of the payload. If the payload can contain sensitive data, store only what your retention and privacy rules allow.
Avoid doing expensive downstream work directly in the webhook request. If the receiver calls several internal services before returning, provider retries can overlap with your own work and create duplicate side effects. A queue lets you control concurrency, retries, and dead-letter handling.
Operational checklist
Before exposing a webhook endpoint, decide how it will be operated:
- Rotate webhook secrets without downtime.
- Reject unsigned or stale requests.
- Store event IDs with a unique constraint.
- Alert on signature failures, retry spikes, and dead-letter events.
- Provide a replay path for safe events.
- Document which event types are ignored intentionally.
A good receiver treats unknown event types carefully. If the provider may add events over time, logging and returning success can be safer than failing and triggering endless retries. If the event type is security-sensitive or impossible under the contract, returning an error may be appropriate. The key is to make the behavior explicit instead of letting every endpoint improvise.
Common mistakes
The first mistake is processing a webhook without verifying the signature. A public endpoint should not trust random JSON.
The second mistake is doing slow work before responding. If processing takes too long, the provider may retry and create duplicates.
The third mistake is not storing event IDs. Duplicate delivery is normal.
The fourth mistake is assuming event order. Design for out-of-order delivery unless the provider gives a strong contract.
Related reading
Read Webhook Signature Verification Explained before trusting incoming webhook payloads. Read Idempotency in APIs Explained for duplicate-safe event handling and Message Queues Explained for async processing after receipt. For retry and status behavior, read Retry Patterns Explained and REST API Status Codes Explained. For the full sequence, browse the API Design Learning Path.