Signing Worker Protocol

Errors and retries

SWP/1 error codes, refusing a request, session failure reasons, when SignatureAPI retries, and the idempotency rules that stop a second signature

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.

Networks fail, and providers have bad days. This page covers how your worker reports a failure, what SignatureAPI does next, and how to make retries safe. The rule behind all of it: a retry must never produce a second signature.

Your worker…Answers withExample
Handled the request, and the act didn’t succeed200 and "status": "failed"The signer refused in the provider’s app
Refused the request itselfA non-200 statusThe token didn’t verify

Failed outcomes

{
    "status": "failed",
    "error": "access_denied",
    "error_description": "The signer refused in the app",
    "retryable": false
}
  • error is one of the codes below. If you need another one, prefix it with x_.
  • error_description is readable text. SignatureAPI keeps up to 500 characters of it.
  • retryable says whether trying the same act again can help. It defaults to false. Set it to true only when the act certainly didn’t happen.

Error codes

errorMeaningretryable
invalid_requestThe body or its fields are malformed or inconsistentfalse
access_deniedThe signer refused, or the provider denied the signerfalse
authorization_expiredThe approval or the waiting window ran outfalse
credential_unavailableNo credential can be found for this signerfalse
credential_expiredA short-lived certificate expired before signingfalse
digest_rejectedThe provider refused the digests: count, algorithm or policyfalse
idempotency_conflictThe key was reused with a different body, or is in flightfalse
temporarily_unavailableThe provider is down or throttlingtrue
internal_errorThe worker failedtrue

Refusing a request

Use a non-200 status only when you refuse the request itself, with a JSON body that has error and error_description:

StatusWhen
400The request is malformed, or the Idempotency-Key is missing
401The token doesn’t verify, or its connection token isn’t allowed. See Authentication.
404Unknown path
409An idempotency conflict
429You are throttling
5xxA worker fault
{ "error": "invalid_request", "error_description": "missing Idempotency-Key" }

Of the 4xx statuses, SignatureAPI retries only 429. On any other, the session fails with a reason that quotes your error and error_description, and the signer can retry from the ceremony.

Session failure reasons

When a session fails, SignatureAPI records a failure reason and the ceremony shows it to the signer, with Try again. Retrying starts a new session.

Reasons your worker chooses

Your worker…Failure reason
Answers failedYour error_description, or your error when you leave the description out
Redirects to callback_url with errorYour error_description parameter, or your error
Refuses a request with a non-200 statusworker refused <operation> with HTTP <status> <error>: <error_description>, for example worker refused sign with HTTP 409 idempotency_conflict: Idempotency-Key sgs_1/sign/1 was used with a different payload

Write a failed answer’s error_description for the signer: short, plain and without internal detail. SignatureAPI keeps up to 500 characters.

Reasons SignatureAPI sets

ReasonWhen
authorization window expiredA pending step passed its deadline.
credential_expired: the signing certificate expired before it could be usedThe credential’s expires_at passed before authorize or sign.
The authorization callback carried neither a code nor an errorThe browser reached callback_url without code or error.

A session can also fail because of what your worker returned, even when it answered completed:

ReasonFormatWhen
worker response invalid: <field>BothA response breaks the protocol. <field> names the problem, for example credential.signature_format, credential.max_signature_size, credential.certificates, sign.certificates, signatures count, pending needs action_url or poll_after or status pending is not allowed on sign.
worker returned non-JSON bodyBothA 200 response isn’t JSON.
max-signature-size-unsupportedcmsmax_signature_size is not an integer from 1 to 53,248.
sign-algorithm-unsupportedBothThe picked algorithm names a hash other than SHA-256. See Supported algorithms. The session fails before authorize.
signature-too-largeBothThe signature doesn’t fit the space reserved in the PDF. For cms, the CMS is larger than max_signature_size, or 24,576 bytes when you didn’t declare one.
signer-certificate-mismatchcmsThe signer certificate in sign differs from the one in credential.
Signature verification failed at the <check> check: …BothThe signature, chain or scheme check failed. See What SignatureAPI checks.
cms-malformed, cms-structure, cms-signer-certificate, cms-message-digest, cms-signing-certificate, cms-signaturecmsThe CMS breaks a requirement.
certificate-not-valid-at-timestampBothA certificate in the chain isn’t valid at the time of SignatureAPI’s timestamp.

Other reasons describe a failure inside SignatureAPI, not in your worker.

Retries and timeouts

credentialauthorizesign
Maximum silence on the connection, per HTTP attempt30 s30 s60 s
HTTP attempts on network errors, timeouts, 429 and 5xxUp to 3Up to 3Up to 3
Calls on failed with retryable: trueUp to 3Up to 31, never retried

The timeouts limit how long the connection may stay silent, not the total time of a call. A worker that keeps sending bytes doesn’t time out.

sign is never retried on a retryable outcome. A wrong retryable: true on credential costs a redundant call. On sign, it could cost a second signature.

SignatureAPI never follows redirects from your worker, and it rejects responses larger than 1 MiB. If your worker stays unavailable after the retries, the session fails and the signer can retry from the ceremony. Every retry carries a new token and the same Idempotency-Key.

Idempotency

Two values protect your worker from repeats:

  • The token’s jti changes on every HTTP attempt. It stops replays.
  • The Idempotency-Key stays the same for one act. It stops double execution.

The key is <session.id>/<operation>/<session.attempt>, for example sgs_YFobK1NDktAuz0wzplR4OP/sign/1. A request that resumes after a redirect is a separate act, so its key ends in /resume.

Idempotent handling is required for sign and recommended for credential and authorize. Follow four rules:

  1. Store only final outcomes. Store completed, or failed with retryable: false. Never store pending or a retryable failure: a repeat of that key runs again.
  2. Handle repeats. If an outcome is stored for the key, return it. If the key is running right now, or arrives with a different body, answer 409 with idempotency_conflict. Otherwise reserve the key, run the act, store the outcome if it is final, then release the key.
  3. Don’t start an act twice. Answer a repeat of an act you already started from your provider’s current state.
  4. Store before you release. Otherwise a retry that arrives between the two finds neither a stored outcome nor a reservation, and signs again.

To detect “same key, different body”, store the body’s SHA-256 next to the outcome. You already computed it while verifying the token.

The case these rules exist for: sign times out after your provider has already signed. The retry carries the same key, you return the stored signatures, and nothing is signed twice.

sequenceDiagram
    participant P as SignatureAPI
    participant W as Signing worker
    participant T as Trust service provider
    P->>W: POST /v1/sign (Idempotency-Key K)
    W->>T: Sign the digest
    T-->>W: Signature
    Note over W: Store the outcome under K
    W--xP: Response lost (timeout)
    P->>W: POST /v1/sign (same K, new token)
    Note over W: K has a stored outcome
    W-->>P: completed (stored signatures)