Signing Workers

Build a signing worker

From your provider's API to a signing worker that signs test envelopes, then live ones

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.

Build a signing worker when nobody runs one for your provider yet. If your provider already runs one, skip to Connect a signing worker.

Before you start

  • Qualified signatures enabled on your SignatureAPI account. Test mode works right away with the sandbox worker.
  • A provider account with API access, ideally with a test environment.
  • A public HTTPS host on port 443, with a certificate from a public certificate authority.
  • A store for idempotency outcomes and seen token IDs, shared by all instances.

1. Map your provider’s API

Find the provider calls behind each operation, and note which steps need the signer. Each of those becomes a pending answer: a redirect (action_url) or a poll (poll_after).

OperationTypical provider call
credentialList the signer’s credentials, or start an identification flow that issues a certificate
authorizeAsk the signer to approve the digests: a consent page, a push to their app, or a one-time password
sign“Sign hash” with the approval

2. Build the endpoints

1

Serve the health check

Answer GET /v1/health without authentication, and list the connection tokens you accept:

app.get("/v1/health", async (req, res) => {
    const providerUp = await provider.ping();
    res.status(200).json({
        status: providerUp ? "ok" : "degraded",
        connection_tokens: [...ALLOWED_CONNECTION_TOKENS],
    });
});
2

Authenticate every POST

Verify the token, body hash and connection token first, then refuse a missing Idempotency-Key with 400. See Verify a request for the order and a Node.js example.

3

Make every operation idempotent

Wrap each operation in one handler keyed by the Idempotency-Key:

async function withIdempotency(key, bodySha256, run) {
    const stored = await store.get(`idem:${key}`);
    if (stored) {
        return stored.bodySha256 === bodySha256
            ? stored.outcome
            : conflict(`Idempotency-Key ${key} was used with a different payload`);
    }
    if (!(await store.reserve(`lock:${key}`))) {
        return conflict(`Idempotency-Key ${key} is currently executing`);
    }
    try {
        const outcome = await run();
        const final = outcome.status === "completed"
            || (outcome.status === "failed" && outcome.retryable !== true);
        if (final) await store.set(`idem:${key}`, { bodySha256, outcome });
        return outcome;
    } finally {
        await store.release(`lock:${key}`); // Only after the outcome is stored
    }
}

conflict answers 409 with idempotency_conflict. The rules are in Idempotency.

4

Implement credential, authorize and sign

Follow each endpoint page. Two details trip up most first versions of sign:

  • Don't hash again. The digest is already a hash.
  • Return DER for ECDSA, not the compact r‖s form many APIs return.

3. Configure connection tokens

Your worker accepts only the accounts whose connection tokens you list. For each account you serve, including your own, open Connect Test Worker or Connect Live Worker (or a connected worker’s card), copy the Connection token, and add it to your worker’s configuration.

4. Test in test mode

1

Try the sandbox worker first

Before you connect anything, send a test envelope with a qualified signer, as in the Quickstart. The sandbox worker at https://sandbox-signer.signatureapi.com signs it: an identification page during credential, an approval page during authorize, then a short-lived ECDSA P-256 certificate. Use it as your reference.

2

Connect your worker for test mode

Connect your worker with Connect Test Worker. Fix any failing connection check.

3

Sign a test envelope

Send the same test envelope. Your worker receives credential, authorize and sign. Check the deliverable's signature in a PDF signature validator.

Also test the paths that are easy to miss:

  • The signer refuses. Your worker answers failed with access_denied.
  • The signer walks away. The step times out after the authorization timeout.
  • A repeated sign. In your own tests, the second call with the same Idempotency-Key returns the stored signatures without calling your provider.

Disconnect your test worker to go back to the sandbox worker for comparison.

5. Go live

  1. Switch your worker to your provider’s production environment, with qualified certificates.
  2. Add the account’s live connection token to your worker.
  3. Connect the worker with Connect Live Worker.
  4. Send a live envelope and check the signature in a validator. The signer should now show as trusted.

See Go live for the account requirements.

Checklist

  • Serve POST /v1/credential, /v1/authorize, /v1/sign and GET /v1/health on a public HTTPS URL, port 443, without redirects.
  • List the connection tokens you accept in /v1/health, and refuse requests carrying any other.
  • Verify ES256, iss, aud, exp, swp.operation, swp.session and swp.body_sha256. Remember each jti until exp plus your clock tolerance.
  • Refuse a request without an Idempotency-Key with 400, after verifying its token.
  • Answer every operation with 200 and completed, pending or failed.
  • Return the full chain, signer certificate first, with honest key_algo_oids and expires_at.
  • Show each digest’s label on any approval screen you host.
  • End a redirect step at callback_url, with a single-use code. Never answer a request carrying callback.code with pending.
  • Follow the idempotency rules. Never sign twice for one key.
  • Sign the right input and return the right encoding.
  • Use only the listed error codes, or x_-prefixed ones.
  • Never log request bodies.