Skip to content

HMAC Authentication & Endpoint Security

This guide consolidates everything you need to secure communication between your system and ProofAge — signing API requests, verifying webhook deliveries, managing secret keys, and rotating them without downtime.

How ProofAge Authenticates Requests

Every API request requires two headers:

HeaderPurpose
X-API-KeyPublic identifier for your workspace.
X-HMAC-SignatureHMAC-SHA256 proof that you possess a valid secret key and the request body has not been tampered with.

The API key identifies who is calling. The HMAC signature proves authenticity and integrity.

Secret Keys

Key Types

Each workspace can have up to 5 secret keys at any time. One key is designated as the active key:

RoleUsage
Any valid keyCan be used to sign API requests. The middleware checks the signature against all non-deleted keys.
Active keyUsed exclusively by ProofAge to sign outbound webhook payloads.

Key Format

sk_live_<56 characters>   # production
sk_test_<56 characters>   # sandbox / testing

Storage

Store secret keys in environment variables or a secrets manager. Never commit them to version control, expose them in client-side code, or log them.


Signing API Requests

Standard Requests (JSON / No Body)

Build a canonical string and compute HMAC-SHA256:

canonical = METHOD + path + body
signature = hex(HMAC-SHA256(canonical, secretKey))
ComponentRules
METHODUppercase HTTP method: GET, POST, PUT, DELETE.
pathRequest path including query string if present: /v1/verifications?page=2.
bodyRaw request body exactly as sent. Empty string for bodyless requests.

Example — signing a POST with JSON body:

POST/v1/verifications/ver_abc123/consent{"consent_version":"2.1","accepted":true}

Multipart Requests (File Uploads)

canonical = METHOD + path [+ '?' + query] + '\n' + sortedFormFields + '\n' + sortedFileHashes
ComponentRules
sortedFormFieldsNon-file fields sorted alphabetically, serialized with RFC 3986 encoding.
sortedFileHashesSHA-256 hex digests of each file's raw bytes, sorted alphabetically, comma-separated.

Multi-Key Behavior

Any non-deleted secret key can be used to sign API requests. ProofAge iterates through all workspace keys to find a matching signature. This enables zero-downtime key rotation — deploy your new key to production, then deactivate the old one once all services are updated.

TIP

The HMAC middleware builds the canonical request once and then tests it against each key, so there is no performance penalty for having multiple keys.


Webhook Signature Verification

ProofAge signs every outbound webhook using the workspace's active secret key.

Headers Included

HeaderDescription
X-Auth-ClientYour workspace's API key (identifies which workspace the webhook belongs to).
X-HMAC-SignatureHMAC-SHA256 hex digest of the signed payload.
X-TimestampUnix timestamp of when the webhook was signed.

Verification Algorithm

  1. Extract X-Timestamp and X-HMAC-Signature from the incoming request.
  2. Read the raw body (do not parse and re-serialize).
  3. Build the signature payload:
    signaturePayload = "{timestamp}.{rawBody}"
  4. Compute HMAC-SHA256(signaturePayload, activeSecretKey).
  5. Compare the computed digest with X-HMAC-Signature using constant-time comparison.

WARNING

The raw body is encoded with JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE. Re-serializing the body may change key order or escaping and break verification.

Replay Protection

Reject webhooks where the timestamp differs from your server time by more than 5 minutes:

abs(currentTime - receivedTimestamp) < 300

Key Rotation

ProofAge supports zero-downtime key rotation:

  1. Create a new secret key in the dashboard (up to 5 keys per workspace).
  2. Deploy the new key to your backend services.
  3. Set the new key as the active key in the dashboard. From this point, ProofAge signs webhooks with the new key.
  4. Update your webhook signature verification to use the new key.
  5. Delete the old key once you have confirmed all services are using the new one.

During steps 2–4, both keys are valid for signing API requests, so there is no window where requests are rejected.

TIP

After changing the active key, your webhook verification must switch to the new key immediately because ProofAge signs all new deliveries with the active key.

Key Limit

Each workspace can hold a maximum of 5 secret keys. If you reach the limit, delete an unused key before creating a new one.


Troubleshooting

INVALID_SIGNATURE

CheckDetail
Method caseMust be uppercase: POST, not post.
Body encodingThe canonical body must be the exact raw bytes sent. No re-serialization.
PathUse path only — /v1/verifications, not the full URL.
Query stringInclude it after the path with ? if present. Same parameter order as sent.
Secret keyConfirm you are using the correct key for the environment (live vs. test).
Hex encodingSignature must be lowercase hex.

NO_SECRET_KEYS

The workspace has no secret keys at all. Generate at least one key from the dashboard.

MISSING_SIGNATURE

The X-HMAC-Signature header was not included. Most endpoints require it — only POST /v1/verifications supports optional signatures.


Further Reading

  • Authentication — Detailed signing examples in Node.js, Python, and PHP.
  • Webhooks — Webhook payload structure, retry policy, and verification code examples.
  • Error Handling — Full error code catalog and best practices.

ProofAge Developer Documentation