The webhook pattern
When a signer finishes a document, your e-signature provider sends an HTTP POST to a URL you control. The two events you usually care about are the moment one signer finishes their part, and the moment the whole packet finishes (all parties signed). Your handler needs three things: a public HTTPS endpoint, a way to verify the request came from your provider, and an idempotent response so retries do not double-process the same event.
Example handler
Anvil's Etch e-signature webhooks deliver a JSON body with three fields: an action name, a verification token, and a data payload. Here is a minimal Node.js handler that validates the token, returns a 204 immediately, and queues the work for an async worker:
const express = require('express')
const app = express()
app.use(express.json())
app.post('/webhooks/anvil', (req, res) => {
const { action, token, data } = req.body
if (token !== process.env.ANVIL_WEBHOOK_TOKEN) {
return res.status(401).end()
}
// Respond fast. Anvil expects a 200 or 204 within seconds.
res.status(204).end()
// Queue the actual work so retries do not back up.
if (action === 'etchPacketComplete') {
queue.add('packet-complete', data)
} else if (action === 'signerComplete') {
queue.add('signer-complete', data)
}
})
app.listen(3000)Register the URL
You can set a static webhook URL globally on your organization in API Settings, or attach a URL per packet when you create it. Per-object URLs override the static one and are useful when different packets belong to different workflows:
await anvilClient.createEtchPacket({
variables: {
name: 'Sample NDA',
files: [/* ... */],
signers: [/* ... */],
webhookURL: 'https://yourapp.com/webhooks/anvil'
}
})Handle retries and idempotency
Anvil retries failed deliveries up to 5 times, at roughly immediate, 5 seconds, 1 minute, 3 minutes, and 7 minutes after the initial call. The last attempt lands about 11 minutes after the first. To survive that retry cycle cleanly, respond with a 200 or 204 as soon as the token is validated and never let downstream work block your response. Treat each event as potentially duplicate by using the packet eid combined with the action name as an idempotency key before writing to your database. If you enable payload encryption with an RSA keypair (recommended in production), decrypt the data field inside the queued worker, not inside the request handler.
Common gotchas
signerComplete fires when one party signs, but the final document is not assembled yet. If you try to download the signed PDF inside the signerComplete handler, you will either get a partial document or a 404. Wait for etchPacketComplete (which only fires once all signers have signed), or for documentGroupCreate, before fetching the final files. During local development, webhook providers cannot reach localhost, so use ngrok or a similar tunnel for testing. If your endpoint sits behind a firewall, allowlist Anvil's two outbound webhook IPs (35.233.165.3 and 34.148.239.131) so the POST is not dropped at the edge.
For the full list of e-signature webhook actions (signerComplete, signerUpdateStatus, etchPacketComplete, documentGroupCreate) and their decrypted payload shapes, see the Anvil Webhooks reference.
Back to All Questions