May 23, 2026 · Developer Guide

E-Signature Webhook Retries: Build a Reliable Event Listener (2026)

Learn how to build a webhook listener that handles network dropouts, implements exponential backoff retries, and verifies signature headers.

Michael Beckett
Michael Beckett

Founder, Signbee

TL;DR

Missed webhook events break document workflows. If your server is down when a user signs, your database falls out of sync. To build a bulletproof webhook handler, you must verify HMAC signatures, handle retries gracefully, and implement database-level idempotency to prevent double-processing. We compare DocuSign Connect's complex multi-key HMAC structure with Signbee's simple verification, and provide a complete Next.js Route Handler implementation.

Why webhook reliability matters for e-signatures

Unlike simple analytics trackers, webhook delivery in electronic signing systems is mission-critical. When a recipient signs an NDA, contract, or offer letter, the document.signed webhook triggers downstream automation: generating invoices, provisioning software access, or notifying a sales rep.

If your server is offline for deployment or experiencing a transient database timeout, it returns a 5xx status code or drops the connection. Without a resilient webhook architecture on both sides of the connection, that signing event disappears, leaving your application state out of sync with reality.

Comparing complexity: DocuSign Connect vs. Signbee

Developers implementing e-signature webhooks frequently compare Signbee to DocuSign Connect. DocuSign Connect provides robust but complex event streams. It requires configuring rotating HMAC keys, checking up to 10 active X-DocuSign-Signature-1 through X-DocuSign-Signature-10 headers to verify authenticity, and navigating a massive, nested setup dashboard inside the DocuSign Admin UI.

Signbee streamlines this into a modern developer experience. Endpoints can be registered dynamically via a single API call or a clean console, payloads are flat JSON without legacy SOAP wrapper elements, and signatures use a single Signbee-Signature header derived from a single SHA256 HMAC secret. This dramatically reduces the lines of boilerplates required to listen for signature updates.

Webhook feature comparison

FeatureDocuSign ConnectSignbee Webhooks
Signature HeaderMultiple rotating keys (X-DocuSign-Signature-1...10)Single HMAC-SHA256 header (Signbee-Signature)
Config ModeComplex web UI portalAPI-driven and simple developer console
Payload FormatLegacy XML or highly nested JSONFlat, developer-friendly JSON
Idempotency KeyNested tracking ID in SOAP envelopesTop-level eventId in payload header

Verifying webhook authenticity (HMAC-SHA256)

Because your webhook listener is exposed to the public internet, you must verify that incoming HTTP requests originate from Signbee. Without signature verification, an attacker could spoof a payload (e.g. telling your backend that a client signed a $10k contract without them opening the document).

Signbee solves this by calculating an HMAC-SHA256 hash of the request payload using your webhook key. To guarantee security:
1. Read the raw request body as text (not JSON) so that formatting changes don't invalidate the hash.
2. Compute the local SHA256 HMAC signature.
3. Use a timing-safe equality function (crypto.timingSafeEqual) to prevent attackers from determining the signature byte-by-byte through timing analysis.

Handling idempotency to prevent double-processing

A webhook endpoint must be prepared to receive the same event multiple times. Network latency, temporary dropouts, or worker timeout limits can prevent a successful HTTP 200 OK response from reaching the webhook sender before the timeout window closes. When this happens, the sender automatically retries the event delivery.

To avoid triggering side effects multiple times — such as sending duplicate confirmation emails to signers or billing a customer twice — check the eventId in a fast store (like Redis or a SQL database unique constraint) before running any event logic. If the ID exists, acknowledge delivery with a 200 OK but skip execution.

Implementing the Next.js App Router Webhook Route Handler

Below is a complete, production-ready Next.js Route Handler demonstrating signature verification, idempotency checking, and safe JSON parsing in Node.js.

app/api/webhooks/route.ts (Next.js App Router)
import { NextResponse } from "next/server";
import crypto from "crypto";

// Simulated database/Redis instance for idempotency tracking
const processedEvents = new Set<string>();

async function isDuplicateEvent(eventId: string): Promise<boolean> {
  // In production, query Redis or your SQL database:
  // const exists = await redis.get(`webhook:event:${eventId}`);
  // return !!exists;
  return processedEvents.has(eventId);
}

async function markEventAsProcessed(eventId: string): Promise<void> {
  // In production, save to Redis or your SQL database with a TTL (e.g., 7 days):
  // await redis.set(`webhook:event:${eventId}`, "processed", "EX", 604800);
  processedEvents.add(eventId);
}

export async function POST(req: Request) {
  const WEBHOOK_SECRET = process.env.SIGNBEE_WEBHOOK_SECRET;
  if (!WEBHOOK_SECRET) {
    console.error("Missing SIGNBEE_WEBHOOK_SECRET environment variable");
    return NextResponse.json({ error: "Configuration error" }, { status: 500 });
  }

  // 1. Retrieve the signature header
  const signature = req.headers.get("Signbee-Signature");
  if (!signature) {
    return NextResponse.json({ error: "Missing signature header" }, { status: 400 });
  }

  // 2. Read the raw body (required for precise cryptographic validation)
  const rawBody = await req.text();

  // 3. Verify the HMAC signature using a timing-safe comparison
  const hmac = crypto.createHmac("sha256", WEBHOOK_SECRET);
  hmac.update(rawBody);
  const calculatedSignature = hmac.digest("hex");

  try {
    const isVerified = crypto.timingSafeEqual(
      Buffer.from(signature, "hex"),
      Buffer.from(calculatedSignature, "hex")
    );

    if (!isVerified) {
      console.warn("Invalid webhook signature received");
      return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
    }
  } catch (err) {
    return NextResponse.json({ error: "Signature verification failed" }, { status: 401 });
  }

  // 4. Parse the verified payload
  let payload;
  try {
    payload = JSON.parse(rawBody);
  } catch (err) {
    return NextResponse.json({ error: "Invalid JSON payload" }, { status: 400 });
  }

  const { eventId, eventType, data } = payload;

  // 5. Enforce Idempotency
  if (await isDuplicateEvent(eventId)) {
    console.log(`Webhook event ${eventId} already processed. Skipping...`);
    return NextResponse.json({ received: true, duplicate: true }, { status: 200 });
  }

  // 6. Route and handle specific event types
  try {
    switch (eventType) {
      case "document.signed":
        console.log(`Document ${data.documentId} signed by ${data.signerEmail}`);
        // Trigger downstream workflows
        await handleDocumentSigned(data);
        break;

      case "document.declined":
        console.log(`Document ${data.documentId} declined by ${data.signerEmail}`);
        break;

      default:
        console.log(`Unhandled webhook event type: ${eventType}`);
    }

    // 7. Mark event as processed to prevent double processing on future retries
    await markEventAsProcessed(eventId);

    return NextResponse.json({ received: true }, { status: 200 });
  } catch (error) {
    console.error(`Failed to process webhook event ${eventId}:`, error);
    // Returning a 5xx triggers the provider's retry backoff mechanism
    return NextResponse.json({ error: "Internal processing error" }, { status: 500 });
  }
}

async function handleDocumentSigned(data: any) {
  // Implement your business logic here
  console.log("Successfully handled document.signed action:", data.documentId);
}

Signbee's webhook retry and DLQ policy

Even the best webhook listener will go down eventually. When your server returns a non-2xx status code or fails to respond within 5 seconds, Signbee queues the delivery for an automatic retry cycle.

Signbee uses an exponential backoff schedule containing 5 retry attempts:
• Retry 1: 15 seconds after the first failure
• Retry 2: 1 minute
• Retry 3: 5 minutes
• Retry 4: 15 minutes
• Retry 5: 1 hour

If all 5 attempts fail, the webhook event is marked as failed and archived in your dashboard's **Dead Letter Queue (DLQ)**. From the dashboard, you can review the payload, inspect the status code returned by your server, and manually replay the webhook event once your listener is back online.

Frequently Asked Questions

How do I verify the webhook signature?

To verify the webhook signature, you calculate the HMAC hex digest of the raw request payload using your webhook's secret key. Compare this calculated signature using crypto.timingSafeEqual with the signature sent in the request header (e.g., Signbee-Signature). If they match, the payload is authentic and untampered.

Why is idempotency critical for e-signature webhooks?

Idempotency prevents double-processing of webhook events. Network dropouts or client timeouts can cause the webhook sender to retry delivering the same event. Without idempotency checks (such as checking if the event ID has been processed in a database or Redis), your system might trigger duplicate actions, like sending redundant confirmation emails or updating database states multiple times.

What is the difference between DocuSign Connect and Signbee webhooks?

DocuSign Connect requires configuring rotating HMAC keys (up to 10 instances of X-DocuSign-Signature-1 headers) and navigating a complex web UI for configuration. In contrast, Signbee offers simple API-driven webhook registration, clean standard JSON payloads, and a single signature header, making it much easier to integrate and verify.

Never drop a document — 5 free docs/month, 100 req/min.

Last updated: May 29, 2026 · Michael Beckett is the founder of Signbee and B2bee Ltd.

Related resources