Worker Recipes

Recipe: one-shot certificate

A signing worker for a provider that identifies the signer, issues a certificate for one signature and returns a complete CMS signature, with the cms signature format

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.

This recipe fits a provider that:

  • has no certificate for the signer before they sign,
  • identifies the signer in its own flow, for example with an identity document and a selfie, a video call or a national eID,
  • issues a one-shot certificate, valid for minutes, and signs with it, and
  • returns a complete CMS signature over a hash.

With the cms signature format, SignatureAPI doesn’t need the certificate until sign. So the whole provider flow, identification, issuance and approval, fits in one authorize interaction. The signer leaves the ceremony once. Not sure this is your provider? See Choose your provider’s model.

The flow

sequenceDiagram
    participant B as Signer's browser
    participant P as SignatureAPI
    participant W as Your worker
    participant Q as Provider
    B->>P: Finish the ceremony
    P->>W: POST /v1/credential
    W-->>P: completed (id, signature_format: cms)
    Note over P: Reserve space, compute the<br/>byte-range digest
    P->>W: POST /v1/authorize (digest, label)
    W->>Q: Start a signing request for the hash
    Q-->>W: Hosted page URL
    W-->>P: pending (action_url)
    P-->>B: Authorize your signature
    B->>Q: Identify, review, approve
    Note over Q: Issue a one-shot certificate
    Q-->>B: Redirect to your worker
    B->>W: Return page
    W-->>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 (signature_format: cms)
    W->>Q: Get the signature
    Q-->>W: CMS and certificate chain
    W-->>P: completed (signatures, certificates)
    Note over P: Check the CMS, replace the<br/>timestamp, embed in the PDF

Your provider’s API decides where the signing act happens. Some sign as soon as the signer approves, and your worker only collects the result in sign. Others wait for an explicit “sign” call. Either fits: sign is where you return the result.

1. credential: declare the format

There is no certificate yet, so answer with an ID and the format:

{
    "status": "completed",
    "credential": {
        "id": "sgs_YFobK1NDktAuz0wzplR4OP",
        "signature_format": "cms",
        "max_signature_size": 16384
    }
}
  • id is any handle you’ll recognize in authorize and sign. The session ID works.
  • max_signature_size is the largest CMS your provider returns, in bytes, from 1 to 53,248. Leave it out for the default, 24,576. A CMS above the limit fails the session with signature-too-large, so measure your provider’s largest output, chain included, and add a margin.
  • No certificates, no key_algo_oids. The certificate arrives in sign. SignatureAPI then doesn’t send sign_algo_oid, and your provider names its algorithm inside the CMS.

Leave out expires_at here. The certificate doesn’t exist yet, so there’s nothing to expire.

2. authorize: the whole provider flow

The digest is the SHA-256 of the PDF’s signed byte range: every byte of the document except the space reserved for the signature. To your provider, it’s the document hash.

  1. Start a signing request at the provider with that hash and the signer’s name and email. Pass the label as the document name, so the signer sees what they sign.

  2. Register your worker’s own return URL with the provider. Map it to this session.

  3. Answer pending with the provider’s page:

    {
        "status": "pending",
        "action_url": "https://provider.example.com/sign/7c1d...",
        "expires_at": "2026-08-24T17:00:00Z"
    }
  4. The signer identifies and approves at the provider. When the provider sends the browser back to your return URL, mint a single-use code and redirect to callback_url with it.

  5. SignatureAPI calls authorize again with "callback": { "code": "..." }. Spend the code and answer:

    {
        "status": "completed",
        "authorization": { "reference": "req_7c1d..." }
    }

Identification can take a while, for example a video call. Set the connection’s Authorization timeout to cover it, up to one hour. See Connect a signing worker. Your expires_at can shorten that deadline, never extend it.

If identification fails or the signer cancels, redirect to callback_url with error=access_denied and an error_description written for the signer.

3. sign: return the CMS and the chain

The request carries "signature_format": "cms" and no sign_algo_oid:

{
    "credential_id": "sgs_YFobK1NDktAuz0wzplR4OP",
    "authorization": { "reference": "req_7c1d..." },
    "digests": [
        { "value": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=", "hash_algorithm_oid": "2.16.840.1.101.3.4.2.1" }
    ],
    "signature_format": "cms"
}

Get the signature from your provider, then answer with the CMS and the one-shot certificate’s chain, signer first:

{
    "status": "completed",
    "signatures": ["MIIGxwYJKoZIhvcNAQcCoIIGuDCC..."],
    "certificates": [
        "-----BEGIN CERTIFICATE-----\n...one-shot signer...\n-----END CERTIFICATE-----",
        "-----BEGIN CERTIFICATE-----\n...issuing CA...\n-----END CERTIFICATE-----"
    ]
}

The CMS must be detached, with one signer, and signed attributes that include the message-digest (equal to the digest you received) and signing-certificate-v2 (naming the signer certificate). The full list, with the failure reason for each rule, is in Requirements on a CMS signature.

Pass the provider’s CMS through as it is:

  • BER is fine. SignatureAPI converts it to DER.
  • A provider timestamp is fine. SignatureAPI replaces it with its own.
  • Leave out revocation data. SignatureAPI adds its own.

Return failed with retryable: false if the provider’s signing failed after the signer approved. Never set retryable: true on sign unless the act certainly didn’t happen. SignatureAPI doesn’t retry sign on a retryable failure in any case.

The signing date in the PDF

The PDF’s signing date is fixed when SignatureAPI computes the digest, before your provider issues the certificate. So the certificate’s validity starts after that date. The trusted time of the signature is SignatureAPI’s timestamp, and the certificate must be valid at that time. Some validators warn about the earlier date. That’s how the format works.

A one-shot provider without CMS

If your provider issues a one-shot certificate but returns only a bare signature value, use raw. Then the certificate must exist before SignatureAPI computes the digest, so issuance moves into credential:

sequenceDiagram
    participant B as Signer's browser
    participant P as SignatureAPI
    participant W as Your worker
    participant Q as Provider
    P->>W: POST /v1/credential
    W-->>P: pending (action_url)
    P-->>B: Confirm your identity to continue
    B->>Q: Identify
    Note over Q: Issue a one-shot certificate
    Q-->>B: Redirect, then callback_url?code=...
    P->>W: POST /v1/credential (callback.code)
    W-->>P: completed (certificates, key_algo_oids, expires_at)
    Note over P: Compute the digest
    P->>W: POST /v1/authorize
    W-->>P: pending, or completed if the<br/>provider session covers the approval
    P->>W: POST /v1/sign (sign_algo_oid)
    W-->>P: completed (signatures)
  • credential pauses for identification. On the resumed call, return the new chain, the key_algo_oids and the certificate’s expires_at.
  • authorize pauses again for the approval. If the provider’s session from the first step already covers this signature, answer completed right away.
  • sign works like the CSC recipe.

SignatureAPI checks expires_at before authorize and again before sign. If the certificate has expired, the session fails with credential_expired: the signing certificate expired before it could be used, and the signer starts again from the ceremony with a new session and a new certificate.

Test it

  • Happy path. A test envelope completes, and a PDF validator shows the signature with the one-shot certificate.
  • Size. Your largest real CMS fits max_signature_size.
  • Identification fails. The ceremony shows your error_description, and Try again starts a new session.
  • The signer walks away. After the authorization timeout, the session fails with authorization window expired.