Server SDK Quickstart

The Server SDK handles the server-side steps of the Relay flow. It abstracts away Privacy Pass cryptography and handles retries automatically.

Looking for full API documentation? See the Server SDK Reference.

Installation

$npm install @persona/sdk-node

Setup

1import Persona from '@persona/sdk-node';
2
3const persona = new Persona({ apiKey: '<your_api_key>' });

Relay methods live on the persona.relays namespace.

Creating a Relay session

Relay Integration Overview

Create the Relay session before starting your selected client-side integration. Store the Relay token and Relay secret on your server — never expose the Relay secret to the client. Return the Relay session access token to your client.

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.

1const { relayToken, relaySecret, relaySessionAccessToken } =
2 await persona.relays.create({
3 claimType: "age_over18_united_kingdom",
4 encryptionKeyPem: "<your_public_key_pem>", // pass null to opt out of encryption
5 });
6
7// Return the Relay session access token to the client
8return { accessToken: relaySessionAccessToken };

Pass the Relay session access token to the Embedded Widget, Hosted Flow, iOS SDK, or Android SDK.

Issuing a Privacy Pass

A Privacy Pass is the billing unit for Relay. It is billed on creation and redeemed to fetch the claim result. Issuance uses your API key and identifies your platform to Persona for billing. Redemption uses the Privacy Pass token instead of your API key. Each Privacy Pass can only be redeemed once. Store privacyPassToken on your server and map it to the corresponding Relay session.

Issuing a Privacy Pass depends only on the claim type — it is not tied to a specific relay, so it can be called at any time, even before the relay is created. One Privacy Pass must exist before you can redeem any relay.

A Privacy Pass expires 90 days after issuance, so avoid issuing too far ahead of when you expect to redeem it.

This step requires your Persona API key — you can find it in the Persona Dashboard under API Keys.

Issue against the same claim type as your relay

The signing key the SDK fetches is determined by the claim type you pass, so each 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 issue against the same claim type you used when creating the relay.

1const { privacyPassToken } = await persona.relays.issuePrivacyPass({
2 claimType: "age_over18_united_kingdom",
3});

Redeeming the claim

Relay Integration Overview

Begin claim retrieval when your client-side integration indicates that the user-facing flow is complete:

  • Embedded Widget: Call your server from onComplete.
  • Hosted Flow: Begin polling your backend when the user launches Hosted Flow.
  • iOS SDK and Android SDK: Call your server after the platform’s normal Inquiry completion mechanism reports that the user-facing experience ended.

The SDK handles the full blind RSA protocol internally.

The Privacy Pass is redeemed only on a successful claim. Since each pass can only be redeemed once, retrying a successful request with the same already-spent token — for example, after a network drop where your server never received the response — would normally result in a double-spend error. Idempotency is handled automatically by the SDK.

1// Redeem the Privacy Pass and retrieve the claim result
2const { claimPayload, tokenConsumed } = await persona.relays.generateClaim({
3 relayToken,
4 relaySecret,
5 privacyPassToken,
6});

Parsing the claim payload

If you opted out of encryption, parse the claim payload directly:

1const claim = JSON.parse(claimPayload);
2
3console.log(claim.claim_type); // e.g. 'age_over_18_united_kingdom'
4console.log(claim.claim_result); // 'passed' or 'failed'
5console.log(claim.methodology); // array of MethodologyCalculation, if not hidden

If you provided an encryption key, decrypt the payload with your private key first.

1import crypto from "crypto";
2
3const decrypted = crypto.privateDecrypt(
4 {
5 key: "<your_private_key_pem>",
6 padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
7 },
8 Buffer.from(claimPayload, "base64"),
9);
10
11const claim = JSON.parse(decrypted.toString("utf8"));
12
13console.log(claim.claim_type); // e.g. 'age_over_18_united_kingdom'
14console.log(claim.claim_result); // 'passed' or 'failed'

See the claim payload schema for the full type definition.

Full example

1import Persona from '@persona/sdk-node';
2import crypto from 'crypto';
3
4const persona = new Persona({ apiKey: '<your_api_key>' });
5
6// Step 1: Create a session and return the Relay session access token to the client
7const { relayToken, relaySecret, relaySessionAccessToken } = await persona.relays.create({
8 claimType: 'age_over18_united_kingdom',
9 encryptionKeyPem: '<your_public_key_pem>',
10});
11return { accessToken: relaySessionAccessToken };
12
13// Step 2 — Issue a Privacy Pass (any time before redemption; depends only on the claim type)
14const { privacyPassToken } = await persona.relays.issuePrivacyPass({
15 claimType: 'age_over18_united_kingdom',
16});
17
18// Steps 1–2 above are server-to-server.
19// After your client-side integration indicates verification is complete, redeem from your server.
20
21// Step 3 — Redeem the Privacy Pass and retrieve the claim result
22const { claimPayload, tokenConsumed } = await persona.relays.generateClaim({
23 relayToken,
24 relaySecret,
25 privacyPassToken,
26});
27
28// Step 4 — Decrypt and parse the claim payload
29const decrypted = crypto.privateDecrypt(
30 {
31 key: '<your_private_key_pem>',
32 padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
33 },
34 Buffer.from(claimPayload, 'base64')
35);
36
37const claim = JSON.parse(decrypted.toString('utf8'));
38
39console.log(claim.claim_type); // e.g. 'age_over_18_united_kingdom'
40console.log(claim.claim_result); // 'passed' or 'failed'