Skip to main content

Check whether a user passed

Your backend can ask Zyphe, at any time, whether a user has passed a verification. You look the user up by the email you gave when you created their verification request, by the customData you attached to it, or by both, and you get back the status of every matching flow result. The answer carries no personal data collected during the verification.

When to use it​

Webhooks remain the way to react to a result as it happens. The search answers a different question, "where does this user stand right now?", and fits when:

  • a user signs in and you need to know whether to let them through, and you do not keep the outcome yourself;
  • you are reconciling your records with Zyphe, for example after your webhook endpoint was down;
  • you want to avoid sending a user through a flow they have already passed.

If you already store the outcome from webhooks, read it from your own records instead. The search is a status check, not a way to fetch results: for the data and documents a user submitted, use the Export API.

Request​

POST https://api.zyphe.com/sdk/organizations/{organization_id}/flow-results/search?sandbox=false
x-api-key: zyphe_sk_...
Content-Type: application/json
PieceValue
Pathorganization_id: your organization's ID
Querysandbox=true for sandbox results, sandbox=false for production (required)
Headerx-api-key with a secret key (zyphe_sk_…). Publishable keys are rejected
BodyThe search criteria below
FieldTypeMeaning
emailstringThe user's email, as given when the verification request was created. Matched exactly, ignoring letter case.
customDataobjectCustom data the flow result must carry. Every key you send must be present with the same value; keys you leave out are not compared.
flowSlugstringLimits the search to one flow. Without it, every flow of your organization is searched.
skipintegerHow many matches to skip. Default 0.
takeintegerHow many matches to return, from 1 to 100. Default 20.

Send at least one of email and a non-empty customData; otherwise the API answers 400. When you send several criteria, a flow result has to match all of them.

customData is matched against the flow result's custom data, the object you passed when creating the verification request (see Custom fields). Searching by your own user ID (for example { "userId": "42" }) is the most reliable lookup, because it does not depend on the user keeping the same email address.

Response​

{
"totalCount": 1,
"page": [
{
"flowResultId": "936f35a8-4921-430e-b016-15be48968886",
"flowId": "2d8285d7-f4ba-42df-ab3f-681d9870d37a",
"flowSlug": "kyc-flow",
"status": "COMPLETED",
"passed": true,
"email": "jane.doe@example.com",
"customData": { "userId": "42" },
"createdAt": "2026-10-01T09:12:44.192Z",
"updatedAt": "2026-10-01T09:20:03.551Z"
}
]
}
FieldMeaning
totalCountHow many flow results match, across all pages.
page[]The matches on this page, most recently updated first.
flowResultIdThe flow result. Use it with the Export API when you need the details.
flowIdThe flow, when the result belongs to one.
flowSlugThe flow's slug.
statusWhere the run stands, in the API status vocabulary (Statuses and codes).
passedtrue when the user passed the verification, that is when status is COMPLETED.
emailThe user's email, when the identity has one.
customDataThe flow result's custom data.
createdAtWhen the run started.
updatedAtWhen the run last changed.

How to read the answer:

You getMeaning
A match with passed: trueThe user passed that flow.
Matches, none with passed: trueThe user started but has not passed. status says whether the run is in progress, in review, or failed. Treat review as pending, not as a rejection.
An empty pageThe user never started a verification that matches, in this environment.

A user can have several flow results, for example one per flow, or a new run after a failed one. Decide on the result for the flow you care about: pass flowSlug, or pick the first match for that slug, since the page is sorted by most recent update.

Node SDK​

import { searchFlowResults, type SDKOptions } from '@zyphe-sdk/node'

const opts: SDKOptions = {
apiKey: process.env.ZYPHE_SECRET_API_KEY!,
environment: 'production',
}

const { data, error } = await searchFlowResults(
{
organizationId: '<organization-id>',
isSandbox: false,
search: {
customData: { userId: '42' },
flowSlug: 'kyc-flow',
skip: 0,
take: 20,
},
},
opts,
)

if (error) {
throw new Error(`Search failed: ${JSON.stringify(error)}`)
}

const hasPassed = data.page.some((result) => result.passed)

searchFlowResults requires a secret key. See Node SDK.

curl​

curl -X POST "https://api.zyphe.com/sdk/organizations/${ZYPHE_ORGANIZATION_ID}/flow-results/search?sandbox=true" \
-H "x-api-key: ${ZYPHE_SECRET_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"email": "jane.doe@example.com",
"flowSlug": "kyc-flow"
}'

Sandbox and keys​

Sandbox and production results are separate. A search with sandbox=true only sees sandbox runs, and a user who passed in sandbox has an empty page in production.

API keys belong to one environment: use a secret key created in sandbox with sandbox=true, and a production secret key with sandbox=false. A key sent with the other environment's flag is rejected with invalid_api_key. See Sandbox mode.

Keep the secret key on your server. Never call this endpoint from a browser or a mobile app.

Errors​

StatusWhen
400Neither email nor a non-empty customData was sent, email is not a valid address, or take is out of range
401Missing or invalid API key, a publishable key, or a key from the other environment