diff --git a/src/app/api/webhooks/documenso/route.ts b/src/app/api/webhooks/documenso/route.ts index c56ff135..c9ccbb59 100644 --- a/src/app/api/webhooks/documenso/route.ts +++ b/src/app/api/webhooks/documenso/route.ts @@ -97,6 +97,13 @@ type DocumensoRecipient = { token?: string | null; signingToken?: string | null; signingUrl?: string | null; + /** Free-text reason the recipient typed into Documenso's reject dialog. + * v2 payloads carry `rejectionReason`; some 1.x payloads use the + * legacy `declineReason` field name. Either way we surface the + * cleartext to the rep so they don't have to log into Documenso to + * see why a deal stalled. */ + rejectionReason?: string | null; + declineReason?: string | null; }; type DocumensoWebhookBody = { @@ -279,9 +286,18 @@ async function handleDocumensoWebhook(req: NextRequest): Promise { const rejecting = recipients.find( (r) => r.signingStatus === 'REJECTED' || r.signingStatus === 'DECLINED', ); + // Documenso uses two field names across versions: v2 + // `rejectionReason`, some 1.x payloads `declineReason`. Coalesce + // so handlers downstream see one stable field. Empty string + // (vs null) normalised to null so the UI's "no reason given" + // copy fires consistently. + const rawReason = rejecting?.rejectionReason ?? rejecting?.declineReason ?? null; + const rejectionReason = + rawReason && rawReason.trim().length > 0 ? rawReason.trim() : null; await handleDocumentRejected({ documentId: documensoId, recipientEmail: rejecting?.email, + rejectionReason, signatureHash, ...portScope, }); diff --git a/src/components/documents/signing-progress.tsx b/src/components/documents/signing-progress.tsx index 13ab5198..a2f078b2 100644 --- a/src/components/documents/signing-progress.tsx +++ b/src/components/documents/signing-progress.tsx @@ -397,20 +397,26 @@ export function SigningProgress({ documentId, signers }: SigningProgressProps) { • `invitedAt !== null` → "Send reminder" (Documenso-side nudge, rate-limited per cooldown). • Signed/declined → no action buttons. */} - {signer.status === 'pending' && signer.signingUrl ? ( + {signer.status === 'pending' ? (