Re-verification and remediation
Verification is not permanent. This is what happens when it lapses, needs more evidence, or has to be withdrawn.
Before you begin
- A cardholder with a current attestation — see KYC lifecycle and statuses.
- A view on your own re-verification policy. We record what you assert; the cadence is yours.
How it works
Three situations bring a verified cardholder back into scope, and they differ in urgency:
| Situation | Trigger | Urgency |
|---|---|---|
| Periodic re-verification | expires_at approaching | Planned |
| Review required | Something needs more evidence | Prompt |
| Revocation | Verification must be withdrawn | Immediate |
All three are handled the same way — by reporting a new attestation, which supersedes the previous one. Nothing is deleted; the chain is what an audit reads.
The chain
Only the newest has is_current: true. The earlier ones stay readable.
Steps
1. Track expiry yourself
Set expires_at when you report an outcome:
{
"status": "approved",
"verified_at": "2026-07-29T11:04:00Z",
"provider": "Acme Identity",
"expires_at": "2028-07-29T00:00:00Z"
}
Then find what is falling due:
curl "$ALETA_BASE_URL/v1/cardholders/chd_01HX8ZQ2K4M7P9RSTVWXYZAB/kyc" \
-H "Authorization: Bearer $ALETA_ACCESS_TOKEN"
There is no cardholder.kyc_expiring_soon event. Drive re-verification from
your own scheduler against the expires_at values you set, the same way you
would any other renewal.
Omitting expires_at means no expiry is tracked at all — which is a valid
choice if your policy has no fixed cadence, but make it deliberately rather than
by forgetting the field.
2. Re-verify and report again
curl -X POST "$ALETA_BASE_URL/v1/cardholders/chd_01HX8ZQ2K4M7P9RSTVWXYZAB/kyc" \
-H "Authorization: Bearer $ALETA_ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"status": "approved",
"verified_at": "2028-06-14T09:22:00Z",
"provider": "Acme Identity",
"provider_reference": "chk_c81f04",
"method": "document_and_selfie",
"expires_at": "2030-06-14T00:00:00Z",
"notes": "Scheduled biennial re-verification"
}'
A new provider_reference — this is a new check, not a re-statement of the
old one. Reusing the previous reference makes the chain unauditable.
3. Handle review_required
Report it when your process needs more from the cardholder but has not concluded:
-d '{
"status": "review_required",
"verified_at": "2028-06-01T10:00:00Z",
"provider": "Acme Identity",
"notes": "Address proof older than 3 months, awaiting replacement"
}'
This blocks card activation immediately, and fires
cardholder.kyc_review_required.
Use notes for operational context. Do not put the evidence itself there — see
identity documents.
4. Understand what happens to live cards
This is the part worth getting right before you need it.
| Reported | Effect on existing active cards |
|---|---|
review_required | No automatic change. Cards keep working |
rejected | No automatic change. Cards keep working |
| Expiry passing | No automatic change. Cards keep working |
Changing kyc_status does not freeze anything. A cardholder whose
verification you have just revoked still holds a spendable card until you act on
it.
That is deliberate — an automatic freeze on every review_required would cut
cardholders off mid-transaction for what is often a paperwork issue. But it
means remediation is a two-step process, and skipping the second step is the
failure mode:
# 1. Record the outcome
POST /v1/cardholders/{id}/kyc { "status": "rejected", ... }
# 2. Act on the cards — this does not happen for you
GET /v1/cardholders/{id}/cards
POST /v1/cards/{card_id}/freeze # reversible, for review
POST /v1/cards/{card_id}/close # terminal, for revocation
Freeze for review_required; it is reversible and the usual outcome is that the
cardholder supplies what was missing. Close for a genuine rejected — see
freeze, replace, close.
5. Restoring after remediation
Report approved again and unfreeze:
POST /v1/cardholders/{id}/kyc { "status": "approved", ... }
POST /v1/cards/{card_id}/unfreeze
A closed card cannot come back. If you closed rather than froze, issue a replacement.
Verifying
- A new attestation appears with
is_current: true, and the previous one is still readable withis_current: false kyc_statusreflects the newest outcome- The matching webhook fired
- Cards were not touched automatically — confirm this in sandbox, because assuming otherwise is the mistake this page exists to prevent
- After freezing, a simulated authorization declines with
card_not_active
Common errors
| Status | code | Fix |
|---|---|---|
422 | kyc_attestation_expired | The attestation in force is past expires_at. Re-verify and report |
422 | kyc_provider_required | Every attestation names who verified, including re-verifications |
422 | kyc_attestation_incomplete | verified_at missing or in the future |
422 | card_closed | Closure is terminal. Issue a replacement instead of unfreezing |