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:

  1. Extract X-PandaDoc-Webhook-Event-Id from the request headers.
  2. Look up the ID in your store of processed deliveries.
  3. 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:

  1. Apply your business logic (or enqueue work for asynchronous processing).
  2. 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).
  3. 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 200

Step 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

  1. Trigger a webhook event that reaches your endpoint.
  2. Confirm your logs show a new X-PandaDoc-Webhook-Event-Id and that processing ran once.
  3. From the Webhooks History tab, retry the same delivery.
  4. Confirm the retry carries the same X-PandaDoc-Webhook-Event-Id and that your handler skips re-processing while still returning success.
  5. If you subscribe to related triggers (for example both document_updated and document_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


Did this page help you?