Verification is backing up, or an automated check failed
Draining the KYC application queue, what every decision does to the applicant, and how to tell a real backlog from a verification service that has lost its credentials.
A stalled verification queue is a revenue stop. KYC level is what gates feature access, so every applicant waiting on review is a customer who cannot deposit, cannot withdraw and — depending on how your levels are built — cannot trade.
There are two shapes of the same symptom and they look identical from the queue screen:
- A real backlog. Applications are arriving faster than they are being reviewed. The fix is throughput.
- A verification service that has quietly stopped working. Its credentials
expired, its key was rotated, or its environment variable was never set on this
box. Nothing errors on a screen anybody looks at; applications simply sit in
PENDING.
Rule out the second before you staff up for the first. It takes two clicks.
The queue
Users → Compliance & Verification → KYC Applications
(/admin/crm/kyc/application).
It opens on the PENDING tab, sorted oldest first — the applicant who has waited longest is the one to review next. Five tabs act as the status filter:
| Tab | Status |
|---|---|
| All | Everything |
| Pending | PENDING — the default |
| Additional Info Required | ADDITIONAL_INFO_REQUIRED |
| Approved | APPROVED |
| Rejected | REJECTED |
Alongside them: a filter by verification level, a filter for service vs manual verification, a sort control (oldest, newest, status, verification type) and a search box. All of it runs server-side, so the search reaches every application rather than the ten rows currently on screen. The KPI cards above the list take the same scope parameters as the list itself, so the figures always describe the population you are looking at.
Each row carries the submission date with an SLA chip beside it — "3d left", or
"2d overdue" — computed against SLA_HOURS.kyc = 168 hours (7 days). Only
PENDING and ADDITIONAL_INFO_REQUIRED rows are tinted; an application approved
a year ago is old, not late.
The same 7-day budget drives the Verification entry in the Operations inbox in the admin header, which turns amber at half the budget (3½ days) and red once anything is past seven.
What the health check is telling you
GET /api/admin/system/health/batch has a KYC Queue entry with two
thresholds, checked in this order:
| Condition | Result |
|---|---|
Any PENDING application older than 7 days |
warning — "N KYC applications pending >7 days" |
Otherwise, PENDING count over 50 |
warning — "High backlog: N pending KYC" |
| Otherwise | up — "N pending KYC applications" |
Both warnings are critical: false, so neither turns the overall health verdict
red. The age test fires first, which is the useful ordering: one seven-day-old
application is a worse signal than fifty that arrived this morning.
Every decision runs through one function
Whether you decide from this screen or a verification service decides for you,
the write goes through applyKycDecision. That is what makes an automated
outcome identical to a human one, and it is what you can rely on having happened:
-
The status is set to
PENDING,APPROVED,REJECTEDorADDITIONAL_INFO_REQUIRED, and your admin notes are saved with it. Notes are sanitised and refused past 5,000 characters. -
reviewedAtis stamped. This is what the average-processing-time figure is computed from, and it is the "Reviewed" line on the application. -
The applicant's cached profile is dropped — but only when the status actually changed. Feature access is read from that cache, so an approval that skipped this would leave the customer locked out of what you had just granted.
-
The applicant is emailed —
KycApproved,KycRejected, orKycUpdatefor "additional info required". APENDINGdecision sends nothing. A mail failure is logged and does not fail the decision. -
An entry is written to the applicant's activity feed —
kyc.approved,kyc.rejectedorkyc.updated— naming the level and, when a service decided, saying so.
Steps 3 and 5 only run on a real change. Step 4 does not. The email goes
out on every call whose resolved status is APPROVED, REJECTED or
ADDITIONAL_INFO_REQUIRED, including one that re-applies the status the
application already carries — a PUT that sends adminNotes without a status
resolves to the current status, so a notes-only edit on an already-approved
application re-sends the KycApproved email. The review screen's three
buttons are each disabled for the status the application is already in, so you
cannot do this by clicking; a script or integration that updates notes can.
The three decisions
Open an application and the review sidebar carries three buttons over an Admin notes box. The application itself has four tabs — Details (the submitted form and its uploaded documents), Verify (the verification service, when the level has one), User and Tips.
| Button | Notes required | What the applicant gets |
|---|---|---|
| Approve application | No | The KycApproved email; their new features unlock immediately |
| Request additional info | Yes | The KycUpdate email; the application moves to ADDITIONAL_INFO_REQUIRED and they can resubmit |
| Reject application | Yes | The KycRejected email, with your notes copied into it |
sendKycEmail copies adminNotes into the rejection email. Both Reject and
Request additional info stay disabled until the box has something in it,
precisely because these are the decisions the applicant is owed an explanation
for. Write the note for the applicant, not for the next reviewer.
If the problem is a blurred document or a mismatched name, Request additional info is almost always the right button. A rejection ends the application; requesting information keeps it alive and lets the customer fix it without starting again.
Is the automated service actually working
Verification services live behind the level builder: open
Users → Compliance & Verification → Verification Levels
(/admin/crm/kyc/level), edit or create a level, and open its verification
services view. Selecting a service there runs the environment check and then, if
that passes, the live connection check.
The two checks answer different questions, and running them in this order is the whole diagnosis:
| Service (id) | Environment variables |
|---|---|
SumSub (sumsub-1) |
SUMSUB_API_KEY, SUMSUB_API_SECRET |
Gemini (gemini-1.5-pro) |
GEMINI_API_KEY |
DeepSeek (deepseek-1) |
DEEPSEEK_API_KEY |
It returns success and a missingEnvVars list. This is the check that tells
you a credential is missing — and a missing credential is the failure that looks
exactly like a backlog, because nothing on the queue screen mentions it.
It sends a /resources/checks request for SumSub, a minimal generation call for
Gemini, and a /v1/models request for DeepSeek, and returns connected plus a
message that distinguishes a missing credential from a rejected one, a rate limit
and an outage. The env check tells you what you forgot to set; the connection
check tells you whether what you set is right.
applyDecision defaults to false. The verdict is written to
kyc_verification_result — status, score, structured checks and per-document
verifications — and the application keeps its current status so a human reads the
verdict first.
Send applyDecision: true and the verdict goes through applyKycDecision exactly
as a manual review would: VERIFIED becomes APPROVED, FAILED becomes
REJECTED, anything else is left PENDING. The applicant is emailed and their
activity feed records that a machine decided, naming the service.
Three services are seeded on a fresh install, and both credential checks
understand all three. The verify route implements only SUMSUB and GEMINI
— a level pointing at DeepSeek answers 400 Unsupported verification service type: DEEPSEEK when you run a check against it.
So a DeepSeek service can pass check-env, pass check-connection and still never
verify anything. If a level's applications are all sitting in PENDING with no
recorded results, check which service the level points at before you go looking
for a credential problem.
Nothing verifies an application automatically on submission. Every service run is
started by an operator from the Verify tab. A queue of PENDING applications
on a service-backed level is therefore normal on an install where nobody presses
the button — it is not evidence of a broken integration.
Draining the queue
-
Prove it is a backlog and not a broken service. If any of your levels use a verification service, run check-env and then check-connection on it first. Two clicks, and it eliminates the failure mode that no other screen reports.
-
Work the PENDING tab from the top. It is already oldest-first. Do not sort by newest; the SLA clock is measured from
createdAt, so the top row is the one closest to breaching. -
Use the level filter to batch like with like. Reviewing thirty Tier 1 identity checks in a row is much faster than alternating between tiers, because you are looking for the same three things each time.
-
Prefer "Request additional info" over "Reject". It keeps the application alive, tells the customer exactly what to fix, and does not force them to start over — which is the path that turns one verification into three support tickets.
-
Clear ADDITIONAL_INFO_REQUIRED too. Those applications are also inside the 7-day SLA and also tinted, and they are the ones most likely to have been resubmitted and forgotten.
If the queue is chronically over 50, the lever is usually the level design rather than staffing — a level asking for four documents when two would satisfy your obligations doubles the review time for every applicant. See KYC: levels, features and the application queue.
Why the backlog costs money
A customer's effective feature set is the union of the features on every level
they hold an approved application for. While their application is PENDING
they hold none of that level's features.
Two settings decide whether those switches bite:
| Setting key | Where | Default |
|---|---|---|
kycStatus |
Settings → Features → Verification → KYC Verification | on |
kycFeatureEnforcement |
Settings → Features → Verification → Enforce KYC Feature Access | off |
With kycFeatureEnforcement on, an unreviewed applicant is refused every
action their approved levels do not list — deposit_wallet, withdraw_wallet,
trade, and the rest. That is the install where a review backlog is a revenue
stop.
With it off, the feature switches on your levels do nothing — but a pending
application still blocks, on a handful of doors. While kycStatus is on, the
doors that enforced KYC before per-feature enforcement existed keep enforcing
their old bar, and that bar is "holds an approved application". A customer
sitting in your queue with only a PENDING one is refused with
KYC verification is required…:
- Creating an API key — needs an approved level 2 or above. This one is not behind any operator toggle.
- Swapping on the DEX (
dexKycRequired), Hummingbot API keys (hbRequireKyc, at its configured level), registering as a gateway merchant (gatewayRequireKyc) and high-value NFT purchases (nftRequireKycForHighValue) — each still behind its own addon toggle, and each requiring an approved application when that toggle is on. - Taking a P2P offer whose maker accepts KYC-verified counterparties only.
Only turning kycStatus itself off makes all of that fall open. So on a default
install the backlog is already costing those customers something; know which of
the two you are running before you decide how urgent the queue is.
One behaviour matters during an incident: an unreadable KYC table fails closed. If enforcement is on and the applications cannot be loaded, every customer is treated as unverified. A settings read error, by contrast, fails open.
Permissions
| Action | Key |
|---|---|
| Open the queue | access.kyc.application + view.kyc.application |
| Approve, reject, request info, edit notes | edit.kyc.application |
| Delete an application | delete.kyc.application |
| Read services, run check-env and check-connection, read results | view.kyc.verification |
| Run a verification | edit.kyc.verification |
| Open and edit verification levels | access.kyc.level, view.kyc.level, edit.kyc.level |
Access to the verification-service screens is a separate grant from the application queue: a reviewer who can approve and reject cannot necessarily prove whether the automated service behind a level is alive.
Related
- KYC: levels, features and the application queue — building levels, the 51 feature switches, and the two settings that make them real.
- The admin panel — the Operations inbox and its targets.
- Roles and permissions — how the keys above are granted.