Guides / Send a document for signature
Send a document for signature
An envelope runs a full e-signature ceremony over your source PDF: you name the signers and place the fields, Rox hosts the signing pages, and when the last signer completes, the final signed document and a completion certificate are sealed as records and anchored like everything else you seal. One API call to send, webhooks to follow along, two sealed artifacts at the end, both publicly verifiable.
1 · Entitlement and pricing: what happens to your ROX
Signatures are a billable service with an explicit switch:
- The sign_ses service must be enabled for your account by a Rox admin before any envelope can be created; without it, creation is refused honestly with RR-3101. Ask your Rox contact to enable it. Your API key also needs the sign scope, granted explicitly like vaults.manage; plain seal or read keys are refused with RR-1005.
- Prices are set in ROX (Rox coin, priced at 1 ROX = 1 USD parity) and shown in your Platform. Creating an envelope places a hold for the service price against your Rox coin wallet: the wallet must cover the price beyond funds already held by other in-flight activities, or the create is refused with RR-3102 naming the price, the available balance and the exact shortfall.
- The hold is captured only on completion, when the last signer has signed and assembly seals the final document. It is released on decline, expiry and void: an envelope that never completes is never charged. Holds and charges appear in your Gas Station ledger like every other ROX movement.
2 · Create and send the envelope
POST /v1/envelopes is one call: the source PDF (PDF only, 50MB cap, %PDF- magic required) plus the envelope name, record type, signing order (sequential or parallel), an optional message and expiry, and the JSON-encoded signers and fields arrays. Field placement is in PDF points, origin bottom-left, 1-based pages; each field references its signer by signer_index into the signers array. Creation IS the send: the envelope lands in status sent. The Node SDK builds the multipart body itself as one buffer and signs those exact bytes:
Integrating without the SDK? Like stamping, the request JWS covers the sha256 of the raw multipart bytes, so build the body with your own boundary:
Send an Idempotency-Key (one per envelope, scoped per vault): a replay with the same key and the same file returns the original envelope with Idempotent-Replay: true; the same key with a different document is refused with RR-2101. Only the ses assurance tier is available today; envelopes naming aes or qes signers are rejected explicitly until those phases ship.
3 · Distribute the signing links
Rox does not email your signers; you hand each signer their link over your own channel. Fetch the envelope and read each signer's signing_url:
Possession of a link only grants the ability to ATTEMPT the ceremony. The signature itself is gated by a one-time code emailed to the signer, so a leaked or forwarded link cannot sign on its own. The link is stable across re-fetches (safe to re-send if a signer loses it) and turns null once the signer or the envelope is terminal.
4 · What the signer walks through
The hosted signing page (mobile-first, no account needed) runs a fixed ceremony and records every step on the envelope's event trail:
- Review. The signer sees the envelope name, your message, who else signs and, on sequential envelopes, whether they are waiting on a predecessor (names and statuses only; other signers' emails and phones are never shown).
- Verify. A 6-digit code goes to the signer's email; entering it proves control of the address. Codes expire and retries are capped.
- Consent. The signer explicitly agrees to sign electronically; the consent text version is recorded on the trail with the timestamp.
- Sign. The signer fills their text and date fields, types their name as signature intent, and draws their signature where signature or initials fields are placed. Declining (with a reason) is always available instead; a decline closes the envelope and releases the hold.
Every step lands as an event (viewed, otp_verified, consented, signed, ...) that you can read back on GET /v1/envelopes/{id} and that feeds the completion certificate's audit trail.
5 · Track progress with webhooks
Subscribe to the envelope.* family and progress pushes to you: envelope.sent, envelope.signer_signed, envelope.signer_declined, envelope.completed (last signer done, assembly starts), envelope.assembled (both records sealed, references on the event), envelope.voided and envelope.expired. Signer objects in payloads carry id, name and order index only, never emails or phones. Full payload shapes live in the event catalogue; verification and retry semantics in the webhooks guide.
Prefer polling? listEnvelopes({ status: 'sent' }) pages with a cursor, and getEnvelope(id) carries everything. To withdraw an envelope before completion, POST /v1/envelopes/{id}/void with a reason; the hold is released and nothing is charged. If assembly ever fails after the last signature (an assembly_failed event on the trail), POST /v1/envelopes/{id}/assemble to re-run it; the call is idempotent and the fee is never captured twice.
6 · Download both artifacts promptly, then verify like a stranger
Assembly produces two sealed records, cross-linked on the envelope:
- The final signed document (completed_record_id): your source PDF with every signature applied, the seal stamp drawn on and the platform's PAdES signature. The sha256 of these final bytes is the sealed digest, so this file, not your original upload, is the document of record.
- The completion certificate (certificate_record_id): the full audit trail of the ceremony (who signed, when, verification and consent events), sealed as its own record.
The presigned URLs expire and the stored file copies leave Rox storage after 1 day, so download and archive both as soon as envelope.assembled fires. The RECORDS and their proofs are permanent regardless. Both verify publicly: scan the stamp on the final document or open scan.roxregister.com and drop the file, verdict first, no account needed, exactly like any other sealed record.
7 · Embed the ceremony in your app (optional)
The same ceremony can run inside your own web app instead of on a full-page link: an insurance app, a bank portal, an onboarding flow. Load the mount script and hand it the signer's signing_url as is; it rewrites the link to the embed route (/embed/{token}), renders the ceremony in an iframe with the chrome minimized, and relays lifecycle events to your page:
Event catalogue. The frame posts { type: "rr-sign", event, envelope_id?, detail? } messages, delivered by embed.js to your onEvent callback:
- ready: the frame loaded and resolved its session (carries envelope_id from here on).
- viewed: a live ceremony is on screen for this signer.
- verified: the signer passed the email one-time code.
- consented: the signer agreed to sign electronically.
- signed: this signer completed; detail.envelope_status is "completed" when they were the last.
- declined: this signer declined (the reason itself stays on the envelope trail, not in the message).
- error: the ceremony cannot proceed; detail.reason is one of invalid_link | expired | voided | declined | completed | rate_limited | unavailable | load_error.
Treat these as UI hints only; your system of record stays the envelope.* webhooks and GET /v1/envelopes/{id}.
White-label branding. Theme the ceremony (hosted page and embed alike) at envelope creation; branding lives on the envelope, never in the URL:
Validation is strict: primary_hex must be an exact #rrggbb color (stored lowercased), name_display is 1 to 120 characters, unknown keys are refused. No arbitrary CSS and no URLs: logo_hidden only toggles the Rox mark; provider logos are a later, reviewed feature.
Security. The ceremony still enforces everything inside the frame: the email one-time code, the consent step, the field checks. Your page only observes lifecycle events; signer emails, field values and document content never ride on a message, and the signing token never leaves the frame's own URL path. The frame posts only to the exact origin your page declared (never a wildcard), and embed.js accepts messages only from the signing origin and only from the mounted frame. The full-page route /s/{token} refuses framing entirely; only /embed/{token} is embeddable.
New to the platform? Start with your first seal for keys and the JWS basics. The final signed document carries the same stamp and PAdES signature as seal and stamp; one root import makes desktop PDF readers trust it fully. And wire up webhooks first: envelopes are asynchronous by nature.