Signing Worker Protocol
Signing Worker Protocol (SWP/1)
How SignatureAPI and a signing worker produce one qualified signature, the shape every request and response shares, and what SignatureAPI checks
Private beta. Qualified signatures are available on request. Request access and we enable them for your account. SWP/1 is in beta and may change.
The Signing Worker Protocol (SWP/1) is the contract between SignatureAPI and a signing worker. A worker serves four endpoints under its base URL:
| Endpoint | Purpose |
|---|---|
POST /v1/credential | Return the signer’s certificate chain and signature format |
POST /v1/authorize | Get the signer’s approval for exactly these digests |
POST /v1/sign | Return one signature per digest, as a raw value or a complete CMS |
GET /v1/health | Report status and list the accounts the worker accepts |
Every POST is authenticated with a token that SignatureAPI signs for that one request.
Lifecycle of a signature
One signer’s turn to sign is a signing session. In a session, SignatureAPI calls the three operations in this order:
credential. Which certificate will this signer sign with?authorize. The signer approves signing exactly these digests.sign. Produce the signature values.
The order follows from how PDF signatures work. A PAdES signature (the standard format for signatures inside a PDF) embeds a hash of the signing certificate. When SignatureAPI builds the signature, it needs the certificate before it can compute the digest. A qualified remote signature binds the signer’s approval to the exact digests, so the digests are final before authorize.
Signature formats
Your worker returns signatures in one of two formats, chosen per credential:
raw, the default. Your provider returns a bare signature value, and SignatureAPI builds the PDF signature around it.cms. Your provider returns a complete CMS (PKCS#7) signature over the PDF’s byte range. The signer certificate can arrive as late as thesignresponse.
Each operation answers completed, pending or failed. A pending answer means the signer must do something at the provider first, such as identify or approve. Your provider decides which calls wait. See Choose your provider’s model and Pending steps.
sequenceDiagram
participant B as Signer's browser
participant P as SignatureAPI
participant W as Signing worker
participant T as Trust service provider
B->>P: Finish the ceremony
P->>W: POST /v1/credential
W-->>P: completed (credential)
Note over P: Compute the digest
P->>W: POST /v1/authorize (digests)
W-->>P: pending (action_url)
P-->>B: Redirect to action_url
B->>T: Identify and approve
T-->>B: Redirect to callback_url?code=...
B->>P: GET callback_url
P->>W: POST /v1/authorize (callback.code)
W-->>P: completed (authorization reference)
P->>W: POST /v1/sign
W->>T: Sign the digest
W-->>P: completed (signatures)
Note over P: Verify, then embed in the PDF
The diagram shows a provider that needs the signer once, during authorize. A provider that identifies the signer in a separate step pauses credential too. One whose signer approves in an app answers poll_after instead of an action_url. The recipes have a diagram for each.
A worker that needs nothing from the signer answers completed three times in a row, and the browser never leaves the ceremony. If any step fails, the session fails, and the signer can retry from the ceremony in a new session.
A session, step by step
From your worker’s side, a signing session moves through these states. Each arrow is one of your answers, or something the signer does.
stateDiagram-v2
direction LR
[*] --> Credential
Credential --> CredentialWaiting: pending
CredentialWaiting --> Credential: callback code, or poll
Credential --> Authorize: completed
Authorize --> AuthorizeWaiting: pending
AuthorizeWaiting --> Authorize: callback code, or poll
Authorize --> Sign: completed
Sign --> Embedded: completed, checks pass
Embedded --> [*]
CredentialWaiting --> Failed: deadline or callback error
AuthorizeWaiting --> Failed: deadline or callback error
Failed --> [*]
Any call can also end the session by answering failed, and so can a signature that fails SignatureAPI’s checks. Between credential and authorize, SignatureAPI computes the digest.
| State | What SignatureAPI does | What the ceremony shows |
|---|---|---|
Waiting, with an action_url | Sends the browser to action_url, and waits for it at callback_url. Polls too if you sent poll_after. | Confirm your identity to continue for credential, Authorize your signature for authorize |
Waiting, with poll_after only | Calls the same operation again with the same body every poll_after seconds | Waiting for your confirmation |
| Failed | Records the failure reason | We couldn’t complete your signature, the reason and Try again |
Try again starts a new session with a new session.id. Nothing carries over from the failed one. See What the signer sees for each screen.
Common request fields
Every POST body carries these fields, plus the operation’s own. Binary values are standard base64 with padding. Timestamps are RFC 3339.
{
"protocol": "swp/1",
"session": { "id": "sgs_YFobK1NDktAuz0wzplR4OP", "attempt": 1 },
"scheme": "eu_eidas",
"signer": {
"id": "re_00ZeZjRaXCgT30n1eBx53N",
"name": "Jane Doe",
"email": "jane.doe@example.com"
},
"callback_url": "https://api.signatureapi.com/v1/signing-sessions/callback?state=..."
}
| Field | Description |
|---|---|
protocol | Always swp/1. |
session.id | The signing session. Key anything you store by it. A retry from the ceremony starts a new session with a new ID. |
session.attempt | Starts at 1. Part of the Idempotency-Key. |
scheme | The legal framework of the signature. Always eu_eidas today. |
signer.id | The recipient ID. The token’s sub claim carries the same value. |
signer.name | The signer’s name, as set on the envelope. |
signer.email | The signer’s email address, or "" when the recipient has none. |
callback_url | Where you send the signer’s browser when an interactive step ends. Treat it as opaque. |
callback.code | Present only when the request resumes after a redirect. The single-use code your worker added to callback_url. |
The signer’s name and email are personal data. Log IDs, not request bodies.
Responses
Answer every operation with HTTP 200 and one of three bodies:
{ "status": "completed", ...result }
{ "status": "pending", "action_url": "https://signer.example.com/consent?ticket=...", "expires_at": "2026-08-24T16:45:00Z" }
{ "status": "failed", "error": "access_denied", "error_description": "The signer refused in the app", "retryable": false }
completedcarries the operation’s result, described on each endpoint page.pendingis allowed oncredentialandauthorizeonly. See Pending steps.failedcarries an error code.
Use a non-200 status only when you refuse the request itself.
What SignatureAPI checks
Before a signature reaches the deliverable, SignatureAPI runs three checks. If one fails, the session fails with that check’s name.
signature. The signature verifies over the exact digest SignatureAPI sent, with the public key of the signer certificate.chain. Each certificate in the chain is signed by the next, and every one was valid at the check’s time.scheme. The session’s scheme iseu_eidas.
For raw, SignatureAPI verifies against the chain it saved when it computed the digest: the one from credential, or the connection’s static certificate. The chain check uses the signing time. A raw sign response carries no certificates.
For cms, SignatureAPI checks the chain from the sign response, at the time that response arrived. Then it verifies the CMS itself: its structure, its message digest, its signature and its signer certificate. See Requirements on a CMS signature.
In both formats, SignatureAPI adds its own timestamp and revocation data before it embeds the signature. Each failure reason is listed in Session failure reasons.
SignatureAPI does not check that the certificate is qualified. Making sure your worker signs with qualified certificates from a qualified provider is the worker owner’s responsibility.
Requirements at a glance
- Public HTTPS. Serve the worker on port 443 with a certificate from a public certificate authority. SignatureAPI connects only to public addresses.
- No redirects. Answer on the URL SignatureAPI called. SignatureAPI never follows a redirect.
- Verify every token, including the body hash against the raw bytes. See Authentication.
- Accept only listed accounts. List your accounts’ connection tokens in
/v1/healthand refuse tokens for any other account. - Single-use codes. Any
codeyou add tocallback_urlworks once. - Sign once. Never sign twice for one
Idempotency-Key. - Small responses. SignatureAPI rejects responses larger than 1 MiB.