API Usage
The API integration gives you full control over the Relay flow. Unlike the Server SDK and Gateway Service, you are responsible for implementing the Privacy Pass protocol yourself.
If your language or runtime has library support for blind RSA cryptography (e.g. @cloudflare/blindrsa-ts), this integration path gives you the most control. Otherwise, consider the Gateway Service to avoid implementing the protocol yourself.
Creating a Relay session
Call this before rendering the widget. Store relay-token and relay-secret securely on your server — never expose these to the client. Return relay-session-access-token to your frontend to initialize the widget.
Recommended: Encrypt your claim payload
We recommend generating an asymmetric key pair so that the claim payload is encrypted and only decryptable by your server.
Once your frontend has the access token, initialize the widget and wait for onComplete before proceeding. See Widget usage.
Issuing a Privacy Pass
A Privacy Pass is the billing unit for Relay — billed on creation and redeemed to fetch the claim result. Since issuance uses your API key, Persona knows your platform identity at this point for billing purposes. Redemption remains fully anonymous.
Issuance depends only on the claim-type — it is not tied to a specific relay, so you can request a challenge and issue a pass at any time, even before the relay is created.
A Privacy Pass expires 90 days after issuance, so avoid issuing too far ahead of when you expect to redeem it.
Get the challenge
Request a challenge for the claim type you want to verify. This call is unauthenticated and returns the values (challenge, token-key, token-key-id) needed to construct a blinded Privacy Pass token.
Issue against the same claim type as your relay
The token-key you receive is determined by the claim-type in your request, so the resulting Privacy Pass is bound to that claim type. A pass can only redeem a relay created with the same claim type — if they don’t match, the pass won’t be able to redeem the claim. Always request the challenge with the same claim type you used to create the relay.
Blind, sign, and unblind
Construct your privacy-pass-token using the parsed challenge values. See Challenge Mechanism for the full protocol — including how to parse the challenge, construct the token input, blind the message, and assemble the final token.
Submit the blinded token for signing. This step requires your Persona API key — you can find it in the Persona Dashboard under API Keys.
Unblind blind-sig using your blinding inverse inv and assemble the final privacy-pass-token. See Challenge Mechanism → Step 4.
Redeeming the claim
onComplete should trigger a call to your server to redeem the Privacy Pass.
The Privacy Pass token is consumed atomically on a successful claim — once redeemed, it cannot be used again. This creates a risk: if a successful redemption request drops mid-flight and your server never receives the response, retrying with the same token would normally result in a double-spend error.
Using idempotency keys
Pass an Idempotency-Key header on every redemption request. Persona caches the result of the first successful request against that key — if you retry with the same key, the original response is returned without attempting to re-redeem the token.
Recommendations:
- Generate the idempotency key once before the first attempt and reuse it on every retry for the same redemption request
- Use a UUID or other cryptographically random string — do not reuse keys across different relay redemptions
- Keys are pruned after 24 hours — if you retry after that window, a new request is generated
Idempotency keys have no effect if the original request failed before the token was consumed. In that case, token-consumed will be false in the response and it is safe to retry without a new token. See Idempotence for how Persona’s idempotency layer works in general.
Parsing the claim payload
If you opted out of encryption, parse claim-payload directly:
If you provided an encryptionKeyPem, decrypt the payload with your private key first:
See the claim payload schema for the full type definition.

