Complete passkey enrollment
/v1/auth/mfa/webauthn/enroll/verifyCompletes passkey enrollment by verifying the attestation response against the nonce sealed in enrollment_token and storing the credential under label. The first factor also returns the one-time recovery codes. Responds 400 when the token is invalid or the attestation fails to verify. Rate-limited to 10 requests per minute.
Authentication
AuthorizationJWT access token. Never send alongside X-API-Key: a request carrying both is refused.
Request body
application/json
enrollment_tokenstring Required The sealed enrollment_token returned by /mfa/webauthn/enroll/options. Carries the WebAuthn challenge nonce server-side.
responseobject Required The PublicKeyCredential JSON returned by `navigator.credentials.create()` — id, rawId, type, response { clientDataJSON, attestationObject, transports }, clientExtensionResults.
labelstring Required User-facing nickname for the factor ("YubiKey 5C", "MacBook passkey").
Responses
application/json
data *MfaWebAuthnEnrollVerifyResponseDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
application/json
error *ApiErrorBodyDto
Rate limit
Returned object
curl -X POST "https://auth.canopy-io.com/v1/auth/mfa/webauthn/enroll/verify" \ -H "Authorization: Bearer $CANOPY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "enrollment_token": "string", "response": {}, "label": "MacBook passkey" }'
{ "data": { "factor": { "id": "00000000-0000-0000-0000-000000000000", "type": "totp", "label": "iPhone 15", "enrolled_at": "2026-04-20T12:00:00.000Z", "last_used_at": "2026-04-20T12:00:00.000Z", "webauthn_rp_id": "acme.com" }, "recovery_codes": [ "ABCD-EFGH-IJKL-MNOP", "QRST-UVWX-YZ23-4567", "..." ], "recovery_codes_generation": 0 } }
Tell us how we can improve this guide.