Skip to main content

Signing ceremony architecture

This page is intended for contributors and internal engineers. Partners do not interact with the signing ceremony directly — the Permissio App handles all signer-facing UX.

Call direction

Partner ──► Permissio API  (POST /v1/envelopes, POST /v1/envelopes/:id/send, …)
Permissio App ──► Permissio API (POST /v1/internal/envelopes/:id/signers/:id/status)

When a partner calls POST /v1/envelopes/:id/send, the API server creates the signing links and notifies the Permissio App. The signer then visits sign.permissio.us to complete the ceremony. The Permissio App is the source of truth for what the signer sees and submits.

Once a signer acts (views the document, signs, or declines), the Permissio App calls back into this API server via the internal endpoint to advance the signer's status. The API server then:

  1. Updates the signer row in the database.
  2. Emits a signer-level webhook event (signer.viewed, signer.signed, or signer.declined).
  3. Recomputes the envelope's aggregate status and, if it changed, updates the envelope row and emits an envelope-level event.

Internal endpoint

POST /v1/internal/envelopes/{envelope_id}/signers/{signer_id}/status
Authorization: Bearer <INTERNAL_SIGNING_SECRET>

This route is not documented in the partner OpenAPI spec. It is protected by a shared secret (INTERNAL_SIGNING_SECRET env var) and rejects any request carrying a partner API key.

Status machine

Signer statuses

FromToTrigger
pendingviewedSigner opens the signing page
pendingsignedSigner signs without first viewing (rare)
pendingdeclinedSigner declines without first viewing
viewedsignedSigner submits their signature
vieweddeclinedSigner declines

Envelope aggregate rules

After every signer status update the API server re-evaluates all signers with role = "signer" (approvers and viewers are excluded):

ConditionNew envelope statusEvent emitted
Any required signer declineddeclinedenvelope.declined
All required signers signedprocessingenvelope.processing
Some (not all) required signers signedpartially_signedenvelope.partially_signed

What processing means

processing is an intermediate state between "all signers have signed" and "the signed PDF is ready". The PDF stamp job runs asynchronously and moves the envelope to completed when it finishes. That transition is handled by the PDF pipeline — not by the signing ceremony endpoint.

Partners polling GET /v1/envelopes/:id will observe the processing status and should wait for completed (or a envelope.completed webhook event) before downloading the signed document.

Environment variable

VariableDescription
INTERNAL_SIGNING_SECRETShared secret between this API server and the Permissio App. Must be a long, high-entropy string. Set in Railway for both services.