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:

EndpointPurpose
POST /v1/credentialReturn the signer’s certificate chain and signature format
POST /v1/authorizeGet the signer’s approval for exactly these digests
POST /v1/signReturn one signature per digest, as a raw value or a complete CMS
GET /v1/healthReport 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:

  1. credential. Which certificate will this signer sign with?
  2. authorize. The signer approves signing exactly these digests.
  3. 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 the sign response.

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.

StateWhat SignatureAPI doesWhat the ceremony shows
Waiting, with an action_urlSends 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 onlyCalls the same operation again with the same body every poll_after secondsWaiting for your confirmation
FailedRecords the failure reasonWe 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=..."
}
FieldDescription
protocolAlways swp/1.
session.idThe signing session. Key anything you store by it. A retry from the ceremony starts a new session with a new ID.
session.attemptStarts at 1. Part of the Idempotency-Key.
schemeThe legal framework of the signature. Always eu_eidas today.
signer.idThe recipient ID. The token’s sub claim carries the same value.
signer.nameThe signer’s name, as set on the envelope.
signer.emailThe signer’s email address, or "" when the recipient has none.
callback_urlWhere you send the signer’s browser when an interactive step ends. Treat it as opaque.
callback.codePresent 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 }
  • completed carries the operation’s result, described on each endpoint page.
  • pending is allowed on credential and authorize only. See Pending steps.
  • failed carries 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 is eu_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/health and refuse tokens for any other account.
  • Single-use codes. Any code you add to callback_url works once.
  • Sign once. Never sign twice for one Idempotency-Key.
  • Small responses. SignatureAPI rejects responses larger than 1 MiB.