You send a document out for signature, and everything works until someone clicks "Decline." The document never reaches your "completed" handler, so nothing downstream fires: the record stays "pending" in your database, the next step in the workflow never runs, and the person who requested the signature is never told it was rejected. Often you only notice days later when someone asks where their contract went.
What's actually happening
Signature platforms emit a set of lifecycle events, and a decline is a terminal event that is separate from completion. Most integrations listen only for the success case (a "completed" or "signed" event) and treat everything else as "still waiting." A declined request is not still waiting, it is finished, just not signed. If your webhook handler has no branch for it, the document has no path out of the pending state. The same trap catches teams that poll for a "completed" status on a timer: anything that is not "completed" looks identical to "in progress."
How to fix it
Handle every terminal event, not just the happy path. At a minimum, branch on completed, declined, canceled or voided, and expired, and give each one an explicit action:
// Map each terminal signing event to an explicit action.
// Non-terminal events (viewed, sent, reminder) fall through to 'wait'.
function handleSignatureEvent(event) {
switch (event.type) {
case 'completed':
return { status: 'completed', action: 'store_signed_pdf' }
case 'declined':
return {
status: 'declined',
action: 'notify_requester',
reason: event.declineReason || 'no reason given',
}
case 'canceled':
case 'voided':
return { status: 'canceled', action: 'close_out' }
case 'expired':
return { status: 'expired', action: 'offer_resend' }
default:
return { status: 'pending', action: 'wait' }
}
}When a decline arrives, capture the reason if the provider supplies one, mark the document as terminated in your own system so it stops showing as pending, and notify whoever requested it. Make the handler idempotent so a duplicate delivery cannot process the same request twice, and give the requester a clean way to correct and resend rather than reopening a dead document.
Two caveats. Event names vary by provider (declined, rejected, and voided can mean the same thing or different things), so check your provider's webhook event reference before you hard-code the strings. And keep the decline in your audit trail: a declined document is not a signed agreement and should never be stored as one, but the fact that someone declined, and when, can matter later.
Back to All Questions