Challenges and replay protection
Every enroll and match needs a fresh, single-use challenge that expires after 60 seconds.
A challenge is a one-time nonce issued by the API. Every POST /v1/enroll, POST /v1/match and POST /v1/me/templates call must include a challenge_id that was just issued for the same app.
curl -X POST https://api.facecard.dev/v1/challenges \
-H "X-FaceCard-Key: fc_pk_live_xxx" \
-H "Origin: https://app.example.com"{
"challenge_id": "ch_...",
"nonce": "<32 bytes, base64url>",
"expires_at": "2026-10-14T12:00:00.000Z"
}Rules
- Single use. A challenge can be used for one call. Request a new one for every attempt.
- 60 second lifetime. An expired challenge is rejected. Expired challenges are deleted hourly.
- Bound to the app. A challenge issued for one app cannot be used with another.
- A challenge is required for every enroll and match, so a captured request body cannot be replayed later to sign someone in.
Why this matters
A face descriptor is just 128 numbers. Without a challenge, anyone who copied the request body of a real sign-in could post it again. The challenge makes each sign-in request unique and short lived, so a recorded request is useless.
Challenges do not prove a live human is in front of the camera. For what the browser checks, see Liveness is advisory.
In the SDK
The SDK requests a challenge for you on every enroll() and signIn(). You only handle challenges yourself when you call the REST API directly.