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 with | Example |
|---|---|---|
| Handled the request, and the act didn’t succeed | 200 and "status": "failed" | The signer refused in the provider’s app |
| Refused the request itself | A non-200 status | The token didn’t verify |
Failed outcomes
{
"status": "failed",
"error": "access_denied",
"error_description": "The signer refused in the app",
"retryable": false
}
erroris one of the codes below. If you need another one, prefix it withx_.error_descriptionis readable text. SignatureAPI keeps up to 500 characters of it.retryablesays whether trying the same act again can help. It defaults tofalse. Set it totrueonly when the act certainly didn’t happen.
Error codes
error | Meaning | retryable |
|---|---|---|
invalid_request | The body or its fields are malformed or inconsistent | false |
access_denied | The signer refused, or the provider denied the signer | false |
authorization_expired | The approval or the waiting window ran out | false |
credential_unavailable | No credential can be found for this signer | false |
credential_expired | A short-lived certificate expired before signing | false |
digest_rejected | The provider refused the digests: count, algorithm or policy | false |
idempotency_conflict | The key was reused with a different body, or is in flight | false |
temporarily_unavailable | The provider is down or throttling | true |
internal_error | The worker failed | true |
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:
| Status | When |
|---|---|
400 | The request is malformed, or the Idempotency-Key is missing |
401 | The token doesn’t verify, or its connection token isn’t allowed. See Authentication. |
404 | Unknown path |
409 | An idempotency conflict |
429 | You are throttling |
5xx | A 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 failed | Your error_description, or your error when you leave the description out |
Redirects to callback_url with error | Your error_description parameter, or your error |
Refuses a request with a non-200 status | worker 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
| Reason | When |
|---|---|
authorization window expired | A pending step passed its deadline. |
credential_expired: the signing certificate expired before it could be used | The credential’s expires_at passed before authorize or sign. |
The authorization callback carried neither a code nor an error | The browser reached callback_url without code or error. |
A session can also fail because of what your worker returned, even when it answered completed:
| Reason | Format | When |
|---|---|---|
worker response invalid: <field> | Both | A 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 body | Both | A 200 response isn’t JSON. |
max-signature-size-unsupported | cms | max_signature_size is not an integer from 1 to 53,248. |
sign-algorithm-unsupported | Both | The picked algorithm names a hash other than SHA-256. See Supported algorithms. The session fails before authorize. |
signature-too-large | Both | The 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-mismatch | cms | The signer certificate in sign differs from the one in credential. |
Signature verification failed at the <check> check: … | Both | The signature, chain or scheme check failed. See What SignatureAPI checks. |
cms-malformed, cms-structure, cms-signer-certificate, cms-message-digest, cms-signing-certificate, cms-signature | cms | The CMS breaks a requirement. |
certificate-not-valid-at-timestamp | Both | A 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
credential | authorize | sign | |
|---|---|---|---|
| Maximum silence on the connection, per HTTP attempt | 30 s | 30 s | 60 s |
HTTP attempts on network errors, timeouts, 429 and 5xx | Up to 3 | Up to 3 | Up to 3 |
Calls on failed with retryable: true | Up to 3 | Up to 3 | 1, 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
jtichanges on every HTTP attempt. It stops replays. - The
Idempotency-Keystays 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:
- Store only final outcomes. Store
completed, orfailedwithretryable: false. Never storependingor a retryable failure: a repeat of that key runs again. - 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
409withidempotency_conflict. Otherwise reserve the key, run the act, store the outcome if it is final, then release the key. - Don’t start an act twice. Answer a repeat of an act you already started from your provider’s current state.
- 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)