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

PathProviderStatus
https://webhook.reri.co/webhook/docusignDocuSign Connect⚠️ approved-inactive

Port: 18790 (shared with hubspot-handler.js — same Express app instance, systemd unit: hubspot-webhook.service) Tunnel: Cloudflare Tunnel reri-apiwebhook.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:

  1. HMAC-SHA256 — DocuSign Connect signs payloads. Handler verifies against DOCUSIGN_WEBHOOK_SECRET env var. Standard HMAC hex comparison.
  2. 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:

FieldTypeDescription
eventstringEvent type (see below)
data.envelopeIdstringDocuSign envelope GUID — used as dedup key
data.envelopeSummary.statusstringEnvelope status: completed, declined, voided, sent, delivered
data.envelopeSummary.completedDateTimestringISO8601 completion timestamp
data.recipients.signersarraySigner list with status and email

Events handled:

  • envelope-completed → mark deal complete, notify Discord
  • envelope-declined → alert Discord + log
  • envelope-voided → update status, log
  • envelope-sent → update status
  • envelope-delivered → log closing docs sent
  • recipient-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:

  1. L1 (In-memory Set): processedEvents.has('envelopeId:event') — fast path within process lifetime.
  2. L2 (Supabase): Query deal_review_activity for existing row with matching action + 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 with action, envelope_id, status, timestamp.
  • Logger: log tagged docusign-webhook via scripts/lib/logger.js.
  • Discord: #betterfiles-build channel notified on completed + declined envelopes.
  • webhook_audit_log — Not directly wired in this handler; parent Express app may write depending on middleware chain.
  • 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-registryhubspot-webhook.service is the parent unit (shared port)