FaceCard Docs
Concepts

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.

On this page