Reference
This page covers the public @passlock/server helpers for mailbox challenges in more detail.
Shared request fields
Section titled “Shared request fields”All mailbox challenge helpers take these shared fields:
tenancyId- your Passlock tenancy IDapiKey- your tenancy API keyendpoint- optional API base URL override, defaults tohttps://api.passlock.dev
Mailbox challenges also share a few important concepts:
purposeis an application-defined string. Passlock validates it as a 1-64 character string using onlyA-Z,a-z,0-9,.,_,:, and-.metadatamust be JSON-compatible.challengeIdidentifies the challenge.secretis stored by your app and sent back during verification.codeis the one-time code you deliver to the user.
createMailboxChallenge
Section titled “createMailboxChallenge”Use createMailboxChallenge to start a flow and obtain the generated challengeId, secret, code, and rendered message content (html and text).
import { createMailboxChallenge } from "@passlock/server";
const result = await createMailboxChallenge({ tenancyId: "myTenancyId", apiKey: "myApiKey", email: "jdoe@example.com", purpose: "signup", userId: "123", metadata: { signupId: "signup_123", }, invalidateOthers: true,});
console.log(result.challenge.challengeId);console.log(result.challenge.secret);console.log(result.challenge.code);console.log(result.challenge.message.html);import { createMailboxChallenge } from "@passlock/server/safe";
const result = await createMailboxChallenge({ tenancyId: "myTenancyId", apiKey: "myApiKey", email: "jdoe@example.com", purpose: "signup", invalidateOthers: true,});
if (result.success) { console.log(result.value.challenge.challengeId); console.log(result.value.challenge.message.text);} else { console.error(result.error.message);}Important create options
Section titled “Important create options”invalidateOthersdeletes other pending challenges with the same purpose and subject.- When
userIdis present, Passlock scopes invalidation byuserId; otherwise it scopes byemail. skipRateLimitbypasses mailbox challenge rate limiting and is usually best left unset.
Create errors
Section titled “Create errors”@error/ChallengeRateLimited@error/Forbidden
getMailboxChallenge
Section titled “getMailboxChallenge”Use getMailboxChallenge to read a pending challenge without exposing the secret or code. This is useful when you need to restore an in-progress flow or show which mailbox is being verified.
import { getMailboxChallenge } from "@passlock/server";
const challenge = await getMailboxChallenge({ tenancyId: "myTenancyId", apiKey: "myApiKey", challengeId: "challenge_123",});
console.log(challenge._tag); // "Challenge"console.log(challenge.email);console.log(challenge.metadata);Get errors
Section titled “Get errors”@error/NotFound@error/Forbidden
verifyMailboxChallenge
Section titled “verifyMailboxChallenge”Use verifyMailboxChallenge to check the code against the stored challenge. Pass challengeId, secret, and the user-supplied code.
import { verifyMailboxChallenge } from "@passlock/server";
const result = await verifyMailboxChallenge({ tenancyId: "myTenancyId", apiKey: "myApiKey", challengeId: "challenge_123", secret: "abc123def-ghi456jkl-mno789pqr", code: "123456",});
console.log(result.challenge._tag); // "Challenge"console.log(result.challenge.purpose);console.log(result.challenge.email);import { verifyMailboxChallenge } from "@passlock/server/safe";
const result = await verifyMailboxChallenge({ tenancyId: "myTenancyId", apiKey: "myApiKey", challengeId: "challenge_123", secret: "abc123def-ghi456jkl-mno789pqr", code: "123456",});
if (result.success) { console.log(result.value.challenge.email);} else { console.error(result.error.message);}Successful verification returns ChallengeVerified with a readable nested challenge. The response excludes the secret and one-time code, so your app should keep validating the returned purpose, email, and userId against local expectations.
Verify errors
Section titled “Verify errors”@error/InvalidChallenge@error/InvalidChallengeCode@error/ChallengeExpired@error/ChallengeAttemptsExceeded@error/Forbidden
deleteMailboxChallenge
Section titled “deleteMailboxChallenge”Use deleteMailboxChallenge when a user cancels a flow or when you want to clear pending challenge state explicitly.
import { deleteMailboxChallenge } from "@passlock/server";
await deleteMailboxChallenge({ tenancyId: "myTenancyId", apiKey: "myApiKey", challengeId: "challenge_123",});deleteMailboxChallenge resolves to { _tag: "ChallengeDeleted" }.
Delete errors
Section titled “Delete errors”@error/Forbidden
If you are not using Node.js, the same behavior is documented in the REST API mailbox challenge reference.