Docs
Firmly Agentic Commerce
Set theme to dark (⇧+D)

Verify Enrollment

POST https://api.firmly.work/api/v1/wallets/agentic-pay/enroll/verify

​​ Overview

Completes a verification step during card enrollment. This endpoint may be called multiple times in sequence — first with an OTP code, then with a FIDO registration result. The response indicates whether enrollment is complete or another verification step is needed.

  • OTP verification: Submit the one-time passcode sent to the cardholder
  • FIDO registration: Submit the iframe result captured via postMessage from the network authentication iframe
  • Multi-step flow: After OTP verification succeeds, the response may return a FIDO registration challenge as the next required step

​​ Authentication

  • x-firmly-authorization (string, required) — API token for authenticating the request

​​ Request Body

  • flow_token (string, required) — Flow token returned from the previous enrollment step (Trigger Enrollment or a prior Verify Enrollment call)

  • type (string, required) — Verification type being submitted. One of:

    • otp — One-time passcode verification
    • embed_iframe — FIDO iframe registration result
  • value (string) — The OTP code entered by the cardholder. Required when type is otp.

  • iframe_result (object) — FIDO registration result captured from the authentication iframe via postMessage. Required when type is embed_iframe. Properties:

    • fidoBlob (string): FIDO credential blob from the iframe
    • assuranceData (string): Assurance data from the iframe

​​ Response

The response takes one of two shapes depending on whether enrollment is complete.

​​ Shape 1 — More Verification Needed

Returned when additional verification steps remain (e.g., FIDO registration after OTP).

  • flow_token (string) — Refreshed flow token for the next verification call

  • verification_methods (array) — Array of remaining verification steps the cardholder must complete Item properties:

    • id (string): Verification method identifier (e.g., FIDO_REGISTER)
    • type (string): Challenge delivery type (e.g., embed_iframe)
    • attributes (object): Contains uri, endpoint, identifier, and payload for rendering the FIDO iframe

​​ Shape 2 — Enrollment Complete

Returned when all verification steps are satisfied and the card is enrolled.

  • flow_token (string) — Final flow token to use when creating a payment intent

  • virtual_card_id (string) — Identifier confirming the card has been successfully enrolled

​​ Code Examples


# OTP verification
curl --request POST \
--url https://api.firmly.work/api/v1/wallets/agentic-pay/enroll/verify \
--header 'Content-Type: application/json' \
--header 'x-firmly-authorization: YOUR_TOKEN' \
--data '{
"flow_token": "eyJhbGciOiJBMjU2S1ci...",
"type": "otp",
"value": "123456"
}'

// OTP verification
const response = await fetch('https://api.firmly.work/api/v1/wallets/agentic-pay/enroll/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-firmly-authorization': 'YOUR_TOKEN'
},
body: JSON.stringify({
flow_token: 'eyJhbGciOiJBMjU2S1ci...',
type: 'otp',
value: '123456'
})
});
const result = await response.json();
if (result.virtual_card_id) {
console.log('Enrollment complete:', result.virtual_card_id);
} else {
console.log('Next step:', result.verification_methods[0].id);
}

import requests
# OTP verification
response = requests.post(
'https://api.firmly.work/api/v1/wallets/agentic-pay/enroll/verify',
headers={
'Content-Type': 'application/json',
'x-firmly-authorization': 'YOUR_TOKEN'
},
json={
'flow_token': 'eyJhbGciOiJBMjU2S1ci...',
'type': 'otp',
'value': '123456'
}
)
result = response.json()
if 'virtual_card_id' in result:
print('Enrollment complete:', result['virtual_card_id'])
else:
print('Next step:', result['verification_methods'][0]['id'])

​​ Response Example


{
"flow_token": "<encrypted-jwe-token>",
"verification_methods": [
{
"id": "FIDO_REGISTER",
"type": "embed_iframe",
"attributes": {
"uri": "https://sbx.vts.auth.visa.com/vts-auth/register?apiKey=...",
"endpoint": "L3JlZ2lzdGVy",
"identifier": "59c3208baeadec4566181b18356a5e02",
"payload": "eyJraWQiOiI0ZDkxZWY5MiIsImFsZyI6IlJTMjU2In0"
}
}
]
}

{
"flow_token": "<encrypted-jwe-token>",
"virtual_card_id": "cf90be5c86363de702ed19beec46e102"
}

​​ Error Responses

Code Status Description
BadRequest 400 Invalid flow_token format or missing required fields
BadRequest 400 Incorrect OTP code submitted by the cardholder — the network (VTS) surfaces a wrong OTP as a 400 BadRequest, not a distinct InvalidOtp literal
ErrorServiceUnavailable 503 Network verification service failed or timed out
400 - Bad Request

{
"code": 400,
"error": "BadRequest",
"description": "Invalid or expired flow_token"
}
400 - Incorrect OTP

A wrong OTP on the Agentic Pay path surfaces as a 400 BadRequest carrying the network (VTS) message — there is no distinct InvalidOtp literal on this path. Branch on 400 BadRequest and prompt the cardholder to re-enter the code. (The legacy InvalidOtp literal is a 401 on the older click-to-pay flow, not this one.)


{
"code": 400,
"error": "BadRequest",
"description": "The OTP code is incorrect. Ask the cardholder to re-enter it."
}
503 - Service Unavailable

{
"code": 503,
"error": "ErrorServiceUnavailable",
"description": "Network verification service temporarily unavailable. Retry after a short delay."
}

  • Trigger Enrollment — Start the enrollment flow and receive the initial challenge
  • Create Intent — Create a payment intent after enrollment is complete