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.

9 min readUpdated 6 August 2026kyc, verification, queue, backlog, runbook

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:

  1. The status is set to PENDING, APPROVED, REJECTED or ADDITIONAL_INFO_REQUIRED, and your admin notes are saved with it. Notes are sanitised and refused past 5,000 characters.

  2. reviewedAt is stamped. This is what the average-processing-time figure is computed from, and it is the "Reviewed" line on the application.

  3. 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.

  4. The applicant is emailedKycApproved, KycRejected, or KycUpdate for "additional info required". A PENDING decision sends nothing. A mail failure is logged and does not fail the decision.

  5. An entry is written to the applicant's activity feedkyc.approved, kyc.rejected or kyc.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:

Reports which required environment variables are missing. Never leaves the box
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.

Makes a real signed call to the vendor and reports whether it worked

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.

Runs a service against one application
Reads back the recorded verdicts

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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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 → FeaturesVerificationKYC Verification on
kycFeatureEnforcement Settings → FeaturesVerificationEnforce 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.