Skip to main content

Verifying identity for profile updates

When a user needs to change their phone number or email but cannot complete the normal sign-in flow, use the Identity Verification API to confirm they are the account owner before applying the update.

Create Challenge → Present Questions → Submit Answers → Apply Profile Update

This flow is separate from KYC verification. KYC unlocks money movement; identity verification authorizes sensitive contact-field changes on an existing account.

When to use this flow

Use identity verification when:

  • A user lost access to their previous phone number or email
  • Support needs a regulated, auditable path to update contact information
  • You want to confirm account ownership before changing login identifiers

For routine profile fields (name, address, and similar data attached to the merchant profile), continue using PATCH /api/v1/users/:userReference/attached-profile.

Prerequisites

Before creating a challenge, the user must have:

  • An active wallet under your merchant
  • A user profile with a resolvable full name (first and last name, or preferred name)
  • A date of birth on file

Questions are also enriched from linked account masks and qualifying wallet transactions when that data exists, but name and date of birth are required.

The verification flow

Step 1: Create a challenge

Endpoint: POST /api/v1/users/:userIdentifier/identity-verification/challenges

Identify the user by Accrue userId or your merchant userReference (externalUserId / stableExternalUserId). Accrue resolves the identifier automatically — no query parameter is required.

const response = await fetch(
`${API_CONFIG.baseUrl}/api/v1/users/${userReference}/identity-verification/challenges`,
{
method: 'POST',
headers: API_CONFIG.headers,
},
);

const { data } = await response.json();
const { challengeId, expiresAt, questions } = data.attributes;

The response contains three multiple-choice questions about:

  1. Full name
  2. Date of birth
  3. Linked account last-four digits or a qualifying wallet transaction

Challenges expire after 30 minutes.

Step 2: Collect and submit answers

Present all three questions to the user, then submit their selections.

Endpoint: POST /api/v1/users/:userIdentifier/identity-verification/challenges/:challengeId/submissions

const submission = await fetch(
`${API_CONFIG.baseUrl}/api/v1/users/${userReference}/identity-verification/challenges/${challengeId}/submissions`,
{
method: 'POST',
headers: {
...API_CONFIG.headers,
'Content-Type': 'application/vnd.api+json',
},
body: JSON.stringify({
data: {
type: 'IdentityVerificationSubmission',
attributes: {
answers: questions.map((question, index) => ({
questionId: question.id,
optionId: userSelectedOptionIds[index],
})),
},
},
}),
},
);

const { data: result } = await submission.json();

Each challenge allows up to three submission attempts. Review passed and failureReason in the response:

failureReasonMeaning
IncorrectAnswerOne or more answers were wrong. The user may retry if attempts remain.
ChallengeExpiredThe challenge TTL elapsed. Create a new challenge.
MaxAttemptsExceededAll attempts were used. Create a new challenge.

When passed is true, the response includes a verificationToken valid for 10 minutes.

Step 3: Apply the profile update

Endpoint: POST /api/v1/users/:userIdentifier/identity-verification/profile-updates

Provide the verification token and at least one of phoneNumber or email.

await fetch(
`${API_CONFIG.baseUrl}/api/v1/users/${userReference}/identity-verification/profile-updates`,
{
method: 'POST',
headers: {
...API_CONFIG.headers,
'Content-Type': 'application/vnd.api+json',
},
body: JSON.stringify({
data: {
type: 'ProfileUpdate',
attributes: {
verificationToken: result.attributes.verificationToken,
phoneNumber: '+12125551234',
email: 'user@example.com',
},
},
}),
},
);

Important behavior

  • Single-use tokens: Each verification token can be consumed once. Request a new challenge if the token expires or is already used.
  • Phone number changes: Updating a user's phone number ends their current sign-in session. If the wallet is open, the user is signed out automatically and must sign in again with the new number. The old number no longer grants access to the existing wallet.
  • Merchant profile sync: Update the user's phone number and email in your system before calling this API. Accrue keeps the merchant user profile in sync with your records, so contact info should match on both sides before and after the update.

For embedded integrations, pass the updated contact info in your user context data when the user returns to the wallet. See Webhooks for profile update events when the wallet is not open.

Error handling

HTTP statusCodeTypical cause
403InsufficientVerificationDataMissing name, date of birth, or profile data needed to generate questions
400UserNotFoundNo active wallet for the identifier under your merchant
400ChallengeNotFoundInvalid challenge ID or verification token
400ChallengeExpiredChallenge or verification token TTL elapsed
400ChallengeFailedChallenge is no longer active (already passed or failed)
400MaxAttemptsExceededChallenge locked after three failed submissions
400ValidationErrorInvalid request body, phone/email format, disallowed field for the token, or ambiguous user identifier

API reference

See the Identity Verification API reference for request and response schemas.