How to Deduplicate Webhook Deliveries
Process each webhook notification once by using the X-PandaDoc-Webhook-Event-Id header, even when PandaDoc retries delivery.
Problem
PandaDoc may deliver the same webhook notification more than once (automatic retries after failures, or manual retries from the Developer Dashboard). Your handler must process each distinct notification once and ignore duplicates safely.
Prerequisites
- Active webhook subscription (see How to Set Up Webhook Notifications)
- Persistent storage available to your webhook endpoint (database, cache, or equivalent)
- Familiarity with your endpoint's request-header access
Solution
Step 1: Read the delivery identifier
Every webhook HTTP POST includes the X-PandaDoc-Webhook-Event-Id header. Its value is a UUID that identifies that delivery.
POST /webhook-handler HTTP/1.1
Host: your-domain.com
Content-Type: application/json
User-Agent: PandaDoc Webhooks
X-PandaDoc-Webhook-Event-Id: 550e8400-e29b-41d4-a716-446655440000
[{"event":"document_state_changed","data":{...}}]Use this header as your de-duplication key. Do not rely only on document IDs or payload contents: one underlying change can produce multiple webhook event types, each with its own X-PandaDoc-Webhook-Event-Id.
Step 2: Check whether you already processed the ID
Before applying business logic:
- Extract
X-PandaDoc-Webhook-Event-Idfrom the request headers. - Look up the ID in your store of processed deliveries.
- If the ID is already recorded, treat the request as a duplicate: return an HTTP status below 400 and stop (do not re-apply side effects).
Step 3: Process new deliveries and record the ID
If the ID is new:
- Apply your business logic (or enqueue work for asynchronous processing).
- Persist the event ID as processed (ideally in the same transaction or with an equivalent atomic write so a crash between processing and recording does not leave gaps).
- Return an HTTP status below 400 to acknowledge receipt.
Conceptual flow:
event_id ← request header "X-PandaDoc-Webhook-Event-Id"
if event_id is missing:
reject or log; do not process as a normal delivery
if event_id already in processed_ids:
return HTTP 200 # already handled
else:
process payload
store event_id in processed_ids
return HTTP 200Step 4: Keep acknowledgments consistent on duplicates
When you recognize a duplicate, still return success (status below 400). Returning an error status causes PandaDoc to retry again and can keep failed deliveries in your webhook history.
Verification
- Trigger a webhook event that reaches your endpoint.
- Confirm your logs show a new
X-PandaDoc-Webhook-Event-Idand that processing ran once. - From the Webhooks History tab, retry the same delivery.
- Confirm the retry carries the same
X-PandaDoc-Webhook-Event-Idand that your handler skips re-processing while still returning success. - If you subscribe to related triggers (for example both
document_updatedanddocument_state_changed), confirm those deliveries carry different event IDs and that both are processed.
Troubleshooting
Missing header
- Confirm you are reading HTTP headers case-insensitively (
X-PandaDoc-Webhook-Event-Id). - Confirm a reverse proxy or API gateway is not stripping custom headers.
Duplicates still re-run business logic
- Persist the event ID before acknowledging, or use an atomic "insert if absent" pattern.
- Deduplicate on
X-PandaDoc-Webhook-Event-Id, not only on document ID or event name in the JSON body.
Legitimate events appear to be skipped
- Different webhook event types from the same document change have different IDs; store and compare the header value, not a hash of the whole payload alone.
- Ensure concurrent handlers cannot both pass the "unseen" check; use a unique constraint or equivalent on the stored ID.
Related
- Webhook Events Reference - Header and delivery specifications
- How to Set Up Webhook Notifications - Initial webhook configuration
- How to Verify Webhook Authenticity - Signature verification
- How to Debug and Monitor Webhooks - Retries and delivery history
- Understanding Webhooks - Idempotency and reliability concepts
Updated 1 day ago
