Signing Worker Protocol
Errors and retries
SWP/1 error codes, refusing a request, 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 your error and error_description, and the signer can retry from the ceremony.
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)