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
postMessagefrom 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 verificationembed_iframe— FIDO iframe registration result
-
value(string) — The OTP code entered by the cardholder. Required whentypeisotp. -
iframe_result(object) — FIDO registration result captured from the authentication iframe viapostMessage. Required whentypeisembed_iframe. Properties:fidoBlob(string): FIDO credential blob from the iframeassuranceData(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): Containsuri,endpoint,identifier, andpayloadfor 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 verificationcurl --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 verificationconst 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 verificationresponse = 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."}
Related Endpoints
- Trigger Enrollment — Start the enrollment flow and receive the initial challenge
- Create Intent — Create a payment intent after enrollment is complete