DocuSign Connect Webhook Handler
Real-time handler for DocuSign envelope lifecycle events. Receives Connect notifications, responds immediately with HTTP 200 (DocuSign requires within 100 seconds), then processes asynchronously: updates HubSpot deal status, notifies Discord #betterfiles-build, and logs to Supabase deal_review_activity. Mounted on the same Express app as hubspot-handler.js (shared port 18790).
Endpoint
| Path | Provider | Status |
|---|---|---|
https://webhook.reri.co/webhook/docusign | DocuSign Connect | ⚠️ approved-inactive |
Port: 18790 (shared with hubspot-handler.js — same Express app instance, systemd unit: hubspot-webhook.service)
Tunnel: Cloudflare Tunnel reri-api → webhook.reri.co
WAF: Cloudflare WAF IP allowlist restricting to DocuSign IP ranges (refreshed every ~90 days — see knowledge-base/security/WEBHOOK-IP-RANGES.md)
Status note: approved-inactive as of 2026-04-21 — 0 DocuSign Connect configurations are currently live. Handler is deployed but not receiving events.
Auth method
Dual-layer security:
- HMAC-SHA256 — DocuSign Connect signs payloads. Handler verifies against
DOCUSIGN_WEBHOOK_SECRETenv var. Standard HMAC hex comparison. - WAF IP allowlist — Cloudflare WAF restricts traffic to DocuSign’s published IP ranges before the request reaches the handler. IP ranges are conceptually refreshed every 90 days (per CLAUDE.md governance rule); staleness flagged by
security-audit-funnel.js.
DocuSign requires HTTP 200 within 100 seconds — handler responds immediately, processes asynchronously via setImmediate/Promise chain.
Payload shape
DocuSign Connect sends JSON envelope event objects. Key fields:
| Field | Type | Description |
|---|---|---|
event | string | Event type (see below) |
data.envelopeId | string | DocuSign envelope GUID — used as dedup key |
data.envelopeSummary.status | string | Envelope status: completed, declined, voided, sent, delivered |
data.envelopeSummary.completedDateTime | string | ISO8601 completion timestamp |
data.recipients.signers | array | Signer list with status and email |
Events handled:
envelope-completed→ mark deal complete, notify Discordenvelope-declined→ alert Discord + logenvelope-voided→ update status, logenvelope-sent→ update statusenvelope-delivered→ log closing docs sentrecipient-signed→ partial progress update
Downstream dispatch
docusign-handler.js
├─ isAlreadyProcessed(envelopeId, event) → dual-layer dedup check
├─ HubSpotClient.updateDeal() → docusign_status + docusign_completed_date fields
├─ Discord notify → DISCORD_BETTERFILES_WEBHOOK (#betterfiles-build)
│ on: envelope-completed, envelope-declined
└─ supabase.from('deal_review_activity').insert() → audit row
Dedup strategy
Dual-layer deduplication:
- L1 (In-memory Set):
processedEvents.has('envelopeId:event')— fast path within process lifetime. - L2 (Supabase): Query
deal_review_activityfor existing row with matchingaction+details->>envelope_id— persistent across restarts.
If found in either layer: returns 200 immediately, skips processing.
Audit trail
deal_review_activity— Supabase table; every handled event inserts a row withaction,envelope_id,status,timestamp.- Logger:
logtaggeddocusign-webhookviascripts/lib/logger.js. - Discord:
#betterfiles-buildchannel notified on completed + declined envelopes. webhook_audit_log— Not directly wired in this handler; parent Express app may write depending on middleware chain.
Related
- webhook-architecture — Cross-handler governance; DocuSign uses HMAC + WAF IP allowlist (not just HMAC); WAF rule expires ~90d
- docusign — DocuSign Connect config, IP ranges, envelope event schema,
integrations/docusign/auth.js - hubspot — HubSpot deal fields updated:
docusign_status,docusign_completed_date; shared port 18790 - _summary — BetterFiles TC workflow is the primary consumer of DocuSign completion events
- cron-timer-registry —
hubspot-webhook.serviceis the parent unit (shared port)