Send Docusign Envelope-Completed Alerts to Your Team's Phones
Verify one Docusign Connect completion event, keep agreement details out of the alert, and send one fixed signal to a shared PingRoom Room.

To send a Docusign envelope-completed alert to a team's phones, configure a Docusign Connect JSON SIM webhook for envelope-completed, verify its HMAC over the exact request body, accept only the intended Connect configuration and account, queue a derived envelope event ID, then send fixed copy to a PingRoom incoming webhook.
That gives a shared Room one small completion signal. Docusign remains the place to inspect the envelope, recipients, signatures, documents, and Connect delivery history.
If Docusign's own email or mobile notifications already reach the right person, use them. Add this relay when a bounded group needs the same completion signal in one Room, without forwarding agreement details onto lock screens.
Decide what the Ping is allowed to say
An envelope-completed event can carry more context than a phone alert needs. This recipe deliberately sends only:
- Title: Agreement completed
- Message: Open Docusign to review the completed envelope
It does not forward the document title, email subject, sender, recipient, signer, custom fields, or document contents. It also does not download the completed document.
The alert means the selected Docusign account emitted envelope-completed. It is not legal advice, proof of enforceability, proof that a human reviewed the final document, authorization to act, or a guarantee that every phone displayed the notification.
What you need
- Docusign API and Connect access for the account you will monitor. Availability varies by Docusign account and plan, so confirm it in your account.
- A public HTTPS receiver and a durable queue.
- A private PingRoom Room whose members should receive the signal.
- PingRoom Pro. A Room can have up to four incoming webhooks.
Create the PingRoom webhook from the Room's settings and save its secret URL in your server-side secret store. The URL is a credential: do not put it in browser code, source control, screenshots, or Docusign event data. The webhook reference documents the current request fields, cooldown, idempotency, and error behavior.
Configure one narrow Docusign event
In Docusign Connect, choose the JSON SIM event model, select only envelope-completed, and enable HMAC signing. Leave optional envelope summary data off unless another consumer truly needs it. This relay uses only the account ID, configuration ID, and envelope ID.
Docusign documents account-level, envelope-level, and recipient-level ways to request event notifications. Use the narrowest configuration that matches your workflow. Record these values as server-side configuration:
DOCUSIGN_CONNECT_CONFIGURATION_ID=24680 DOCUSIGN_ACCOUNT_ID=12345678 DOCUSIGN_HMAC_SECRETS=current-key,previous-key-during-rotation PINGROOM_WEBHOOK_URL=https://api.pingroom.io/api/webhooks/...
Do not treat the example IDs as real credentials. During HMAC key rotation, accept the current and immediately previous keys only for the planned overlap, then remove the old key.
Verify, filter, and queue before acknowledging
Docusign computes HMAC-SHA256 over the unmodified request body and sends the Base64 digest in a numbered X-Docusign-Signature-* header. Verify those bytes before parsing JSON. Reformatting the body first will change the digest.
The receiver below keeps the durable job to one derived string. Your queue needs an atomic putIfAbsent(key, value) operation backed by durable storage, not an in-process Map in production.
import { createHmac, timingSafeEqual } from "node:crypto"; const MAX_BODY_BYTES = 128 * 1024; function headerValues(headers, prefix) { const expected = prefix.toLowerCase(); return Object.entries(headers) .filter(([name]) => name.toLowerCase().startsWith(expected)) .map(([, value]) => String(value)); } function signatureMatches(rawBody, signature, secret) { const received = Buffer.from(signature, "base64"); const expected = createHmac("sha256", secret).update(rawBody).digest(); return ( received.length === expected.length && timingSafeEqual(received, expected) ); } export async function acceptDocusignWebhook({ rawBody, headers, hmacSecrets, expectedConfigurationId, expectedAccountId, queue, }) { const bytes = Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(rawBody ?? ""); if (bytes.length > MAX_BODY_BYTES) return { status: 413, code: "payload_too_large" }; const signatures = headerValues(headers, "x-docusign-signature-"); const valid = signatures.some((signature) => hmacSecrets.some((secret) => signatureMatches(bytes, signature, secret)), ); if (!valid) return { status: 401, code: "invalid_signature" }; let event; try { event = JSON.parse(bytes.toString("utf8")); } catch { return { status: 400, code: "invalid_json" }; } if (event?.event !== "envelope-completed") return { status: 200, code: "ignored_event" }; if (String(event?.configurationId ?? "") !== expectedConfigurationId) { return { status: 200, code: "ignored_configuration" }; } if (String(event?.data?.accountId ?? "") !== expectedAccountId) { return { status: 200, code: "ignored_account" }; } const envelopeId = String(event?.data?.envelopeId ?? "").toLowerCase(); const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; if (!uuid.test(envelopeId)) return { status: 400, code: "invalid_envelope_id" }; const eventKey = `docusign:envelope-completed:${expectedAccountId}:${envelopeId}`; try { const inserted = await queue.putIfAbsent(eventKey, { eventKey }); return { status: 200, code: inserted ? "queued" : "duplicate", eventKey }; } catch { return { status: 503, code: "queue_unavailable" }; } }
Return HTTP 200 only after the durable insert succeeds, including for an already-stored duplicate. Docusign asks listeners to respond quickly and recommends asynchronous processing. A queue failure should return a retryable failure rather than acknowledge an event you did not retain.

Pingo, PingRoom's fictional CMO, keeps the rule admirably short here: the shared tray gets a completion token; the detailed papers stay in Docusign. He has no access to anyone's Room or envelopes—the human team owns the configuration and claims.
Let a worker send fixed copy
The worker reads the tiny queued job, not the original Docusign payload. Reuse the same event key as both PingRoom's Idempotency-Key and correlation_id.
export async function deliverCompletionSignal({ job, pingroomWebhookUrl, fetchImpl = fetch, }) { const destination = new URL(pingroomWebhookUrl); if (destination.protocol !== "https:") throw new Error("HTTPS required"); const result = await fetchImpl(destination, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", "Idempotency-Key": job.eventKey, }, body: JSON.stringify({ title: "Agreement completed", message: "Open Docusign to review the completed envelope", correlation_id: job.eventKey, }), }); if (result.status === 429 || result.status >= 500) return { outcome: "retry" }; if (!result.ok) return { outcome: "permanent_failure" }; const payload = await result.json(); return payload?.success === true ? { outcome: "delivered" } : { outcome: "retry" }; }
Retry network failures, HTTP 429, server errors, and malformed success responses with capped backoff. Treat other 4xx responses as configuration failures that need human review. PingRoom's idempotency key makes a downstream retry safe when the same key and body are reused; it does not make Docusign or the network exactly-once.
Test the boundaries before using a real envelope
The production test fixture for this guide checks:
- A valid current or previous HMAC key accepts the exact raw body.
- An invalid signature, malformed JSON, or oversized body is rejected.
- Another event, Connect configuration, or account is ignored.
- A malformed envelope ID is rejected.
- A repeated envelope-completed event produces one durable job.
- The queued job and PingRoom request contain none of the fixture's subject, sender, recipient, signer, or email values.
- Queue failure returns a retryable response; downstream 429, server, and network failures stay retryable.
These are deterministic mocks, not a live Docusign or phone-delivery test. Before production, send a Docusign test event, inspect the Connect log, confirm exactly one queue record, then fire a low-stakes test envelope to a private Room. Check Room history as well as a locked test phone: operating-system policy, connectivity, Room mutes, and notification settings can delay or suppress device display.
Know which system answers which question
| Question | Source of truth |
|---|---|
Did the selected Connect configuration emit envelope-completed? | Docusign Connect log |
| Who signed, what changed, and what is in the document? | Docusign envelope and audit history |
| Did the relay durably accept this envelope event? | Your queue and relay logs |
| Did PingRoom accept the fixed Room Ping? | PingRoom webhook response and Room history |
| Did every phone display it? | Not guaranteed; check device and notification state |
The boundary matters. A completion alert can prompt the team to review the authoritative record; it should not silently authorize the next business action.
Sources
- Docusign: Connect overview
- Docusign: Implementing Connect
- Docusign: JSON SIM event model
- Docusign: Manually authenticating HMAC signatures
- PingRoom: Turn a webhook into a push notification
Send one careful completion signal
Create or open a private Room, add an incoming webhook, and test the fixed alert with non-sensitive data. Keep Docusign authoritative, keep the Room audience small, and make the next human step explicit outside the notification.
PingRoom
Short-form communication.


