Skip to main content
The server SDKs sign every request for you. Read on if you call the API without one.

Keys

Every workspace has two kinds of keys, shown in the console on the workspace’s page. A workspace can hold several secret keys so you can rotate without downtime: any of them signs requests. See Workspaces and keys.

Signing a request

Send two headers on every request:
The signature is the lowercase hex HMAC-SHA256 of a canonical string, keyed with a secret key:
  • METHOD is upper case: POST, GET.
  • path includes the version and no host: /v1/verifications.
  • The query string, when there is one, is appended after a ?, with its parameters sorted by name and encoded as RFC 3986 (spaces as %20, a comma as %2C). GET /v1/verifications?status=approved,declined&limit=20 is signed as GET/v1/verifications?limit=20&status=approved%2Cdeclined.
  • The body is the exact bytes you send. A GET has an empty body.
For example, creating a verification:
is signed over:

File uploads

A multipart/form-data request, such as a media upload, is not signed over its raw body. Its canonical string has three lines:
For a selfie upload with the single field type=selfie:
Nested fields are sorted at every level. The signature is the hex HMAC-SHA256 of that string with the secret key, as for any other request.

Code

The multipart helpers sort only the top level of fields; if you send nested form fields, sort each level the same way.

Creating without a signature

POST /v1/verifications also accepts a request with only X-API-Key, so a page can start a verification with the public key alone. Such a request cannot tie the verification to your user: the API drops external_id and callback_url from it. Create verifications on your backend, signed, whenever you need to know whose verification it is. Every other endpoint requires the signature.

Authentication errors

The body is {"error": {"code": "…", "message": "…"}}. All codes are on Errors.

When the signature does not match

  1. Sign the exact bytes you send. Serialise the JSON once, sign that string, and send that string; a client that re-encodes the body after signing changes it.
  2. Use the path with /v1 and without the host.
  3. Include the query string, with its ?, sorted by parameter name.
  4. Use a secret key of the same workspace as the public key: a test key does not sign for a live workspace.
  5. Send the signature as lowercase hex.

Keep the secret key secret

  • Sign on your server only. Browser and mobile code should call your backend, which calls ProofAge.
  • Keep keys in environment variables or a secrets manager, never in source control.
  • Rotate by adding a new secret key, deploying it, then deleting the old one. See Workspaces and keys.