Developer Guide · Webhooks

E-signature webhooks

Webhooks let your application react to signing events in real time — no polling required. This guide covers which events are fired, how to verify HMAC signatures, how to build an idempotent handler, and how to test locally.

Webhook events

EventWhen firedTypical action
envelope.sentSigning links generated and emails dispatchedLog the send; start your reminder timer.
signer.viewedA signer opens the signing pageUpdate signer status; useful for support and follow-up.
signer.signedA signer completes their signatureAdvance routing UI; notify next signer if sequential.
envelope.completedAll signers have signed; document is sealedDownload the sealed PDF; update your DB; notify parties.
envelope.declinedA signer explicitly declines to signNotify the sender; decide whether to void or re-send.
envelope.voidedSender cancels the envelopeMark your record void; cease reminders.
envelope.expiredSigning deadline passes unsignedNotify sender; prompt to re-send with a new expiry.

Reliable webhook handler pattern

Three rules for a production-ready webhook endpoint: verify the signature, acknowledge fast, process idempotently.

Node.js / Express example
import crypto from 'crypto';
import express from 'express';

const app = express();
app.use(express.raw({ type: 'application/json' })); // raw body for HMAC

app.post('/webhooks/getsigned', async (req, res) => {
  // 1. Verify HMAC-SHA256 signature
  const sig = req.headers['x-getsigned-signature'];
  const expected = crypto
    .createHmac('sha256', process.env.GETSIGNED_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');
  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Acknowledge immediately
  res.status(200).send('ok');

  // 3. Process idempotently in the background
  const event = JSON.parse(req.body);
  const alreadyProcessed = await db.processedEvents.findOne({ eventId: event.event_id });
  if (alreadyProcessed) return;

  await db.processedEvents.insertOne({ eventId: event.event_id });

  if (event.event === 'envelope.completed') {
    // Download sealed PDF from /v1/envelopes/{id}/document
    // Update your database, notify parties, etc.
  }
});
1

Verify before trusting

Compute HMAC-SHA256 of the raw body with your webhook secret. Use timingSafeEqual — never a regular string comparison.

2

Acknowledge in < 5s

Send 200 OK immediately. Move heavy processing to a background job. Slow responses trigger retries.

3

Deduplicate on event_id

Store processed event IDs. Retries are normal — your handler must be safe to call multiple times for the same event.

Frequently asked questions

What is an e-signature webhook?

An e-signature webhook is an HTTP POST request your e-signature service sends to a URL you control when a signing event occurs — for example, when a signer views a document, completes a signature, or when the last signer finishes and the document is sealed. Webhooks let your application react to signing milestones in real time without polling the API.

What events does GetSigned send webhooks for?

GetSigned fires webhooks for: envelope.sent (signing links dispatched), signer.viewed, signer.signed, envelope.completed (all signers done, document sealed), envelope.declined, envelope.voided, and envelope.expired. The most important event for most integrations is envelope.completed — this is when the sealed PDF is ready to download.

How should I verify a webhook payload is authentic?

GetSigned signs each webhook payload with an HMAC-SHA256 signature using your webhook secret, delivered in the X-GetSigned-Signature request header. To verify: compute HMAC-SHA256 of the raw request body using your secret, then compare in constant time to the header value. Never compare with a standard equality check — use a constant-time comparison to prevent timing attacks.

What HTTP status should my webhook endpoint return?

Return 200 OK as quickly as possible — ideally within 5 seconds. If your handler needs to do significant work (database writes, downstream API calls), acknowledge with 200 immediately and process asynchronously in a background job or queue. GetSigned will retry failed deliveries (non-2xx or timeout) with exponential backoff.

How do I make my webhook handler idempotent?

Each webhook event has a unique event_id in the payload. Before processing, check whether you have already handled that event_id (store processed IDs in your database). If yes, return 200 immediately without reprocessing. This prevents duplicate side effects when GetSigned retries a delivery your server acknowledged too slowly or with a transient error.

How do I test webhooks locally during development?

Use a tunneling tool such as ngrok or Cloudflare Tunnel to expose your local server at a public HTTPS URL. Register that URL as your webhook endpoint in the GetSigned dashboard, then trigger events through the API. Alternatively, GetSigned provides a webhook test tool in the dashboard that fires synthetic events to your endpoint.

Related: Integration guide · Node.js guide · Multi-signer routing · Audit trail guide

Start building with webhooks today

Get free API keys →