Swap the KYC vendor¶
Goal: replace Sumsub with a different KYC / identity-verification vendor (Onfido, Veriff, Jumio, etc.). Audience: customer engineering team mandated to use a specific KYC provider for compliance. Time: 1–2 weeks code + 2–4 weeks compliance / legal sign-off. Friction: very high — the current KYC abstraction is vendor-specific, not strategy-pattern. Plan to rewrite, not swap.
What you'll change¶
| Layer | Path | Action |
|---|---|---|
| KYC module | apps/api/src/kyc/ |
Significant rewrite. Replace sumsub/ subfolder with <vendor>/ and adapt the service calls. |
| KYC controllers | apps/api/src/kyc/controller/{kyc,kyc-webhook,admin.kyc}.controller.ts |
Adapt webhook signature + payload to vendor spec. |
| KYC repository | apps/api/src/kyc/kyc.repository.ts |
Likely unchanged (state machine is vendor-agnostic). |
| Prisma schema | libs/_prisma/src/schema/api.prisma (search KycLevel, KycRejectType, KycVerificationVerdict) |
Verify enums cover new vendor's verdicts. Migration if not. |
| Doppler | .example.env SUMSUB_* keys |
Replace with new vendor keys. |
| Frontend KYC widget | ebit-fe/src/... (search Sumsub) |
Replace iframe / SDK with vendor's. |
| Compliance | (legal) | Re-verify customer's KYC obligations against the new vendor's coverage. |
Canonical references¶
Verified file inventory (2026-04-25):
apps/api/src/kyc/
├── kyc.module.ts
├── kyc.service.ts # high-level orchestration
├── kyc-crud.service.ts # DB-only operations
├── kyc.repository.ts # Prisma calls
├── const.ts # cache keys, TTLs, webhook key
├── controller/
│ ├── kyc.controller.ts # user-facing endpoints
│ ├── kyc-webhook.controller.ts # provider callbacks
│ └── admin.kyc.controller.ts # backoffice review
├── dto/
├── sumsub/
│ ├── sumsub.service.ts # outbound Sumsub API + webhook signature verify
│ ├── sumsub.dto.ts
│ └── utils.ts
└── utils.ts
Read sumsub.service.ts first — every public method is a Sumsub-namespaced call, not an interface implementation. That's the friction.
Doppler keys (per .example.env):
Steps¶
1. Compliance pre-flight [non-engineering]¶
Before any code:
- [ ] Confirm the new vendor is licensed for every jurisdiction the customer operates in.
- [ ] Confirm the vendor's KYC levels align with our internal state machine (
KycLevel: BASIC, FULL, ENHANCED, etc. — check Prisma enum). - [ ] Confirm the vendor exposes the same verdict types we use (
approved,rejected_temporary,rejected_permanent,pending,re-verify). If not, agree on the mapping. - [ ] Get sign-off from the customer's legal / compliance officer in writing.
- [ ] Confirm document retention policy alignment (vendor's data-retention vs the customer's legal requirements).
2. Decide: refactor or rewrite¶
Two paths:
Option A — direct replacement (faster, similar shape). Copy kyc/sumsub/ to kyc/<vendor>/, replace the API calls, swap webhook signature verification. Update KycService to inject the new module instead of SumsubService. Plan: 1–2 weeks. No interface introduced.
Option B — strategy pattern (slower, future-proof). Introduce KycProviderInterface + an @Inject('KYC_PROVIDER') token. Implement two adapters (Sumsub, new vendor) gated by env (KYC_PROVIDER_NAME). Plan: 3–4 weeks. Useful if the customer expects to rotate KYC vendors. Coordinate with the platform team — this changes the central abstraction.
Most customers want Option A. Pick Option B only with explicit platform-team buy-in. The rest of this recipe assumes Option A.
3. Set up vendor sandbox creds¶
Add to .example.env:
# Replace the SUMSUB_* block with:
KYC_PROVIDER_NAME="yourvendor"
YOURVENDOR_API_URL="https://sandbox.yourvendor.example/v1"
YOURVENDOR_API_KEY=""
YOURVENDOR_WEBHOOK_SECRET=""
Set the real values in Doppler. Keep SUMSUB_* available as fallback during the cutover.
4. Scaffold the new vendor module¶
cp -r apps/api/src/kyc/sumsub apps/api/src/kyc/yourvendor
# rename: yourvendor.service.ts, yourvendor.dto.ts, utils.ts
Adapt yourvendor.service.ts:
- Replace the constructor's
SUMSUB_*config reads withYOURVENDOR_*. - Replace each outbound method (
generateAccessToken,getApplicationStatus, etc.) with the new vendor's API path + payload. - Replace
verifyWebhookSignaturewith the new vendor's signature scheme. Constant-time comparison only, perdocs/security-register.md. - Map the new vendor's verdicts onto our
KycVerificationVerdictenum.
5. Update the webhook controller¶
Edit apps/api/src/kyc/controller/kyc-webhook.controller.ts:
- Change the webhook path if needed (Sumsub's path is fixed; switching vendor likely means a new path on the customer's side too).
- Inject
YourVendorServiceinstead ofSumsubService(or, in Option B, the strategy token).
The webhook handler must:
- Verify signature.
- Map vendor verdict → internal verdict via
KycRepository. - Update
User.kycLevel. - Emit any downstream events (notification, admin alert) that Sumsub currently emits.
Document any divergence in the verdict mapping in docs/api/changelog.md.
6. Frontend integration¶
Search ebit-fe/src/ for sumsub (case-insensitive). Typical hits:
- A KYC widget loader (Sumsub's WebSDK).
- A "Start verification" CTA that fetches an access token from the backend.
Replace with the new vendor's SDK loader. The new vendor likely supplies an embeddable iframe or JS SDK — follow their docs.
The backend access-token endpoint stays the same path (POST /kyc/access-token or similar — verify in docs/api-reference/api.md "KYC API"); only the response payload type changes.
7. Wire the module¶
Edit apps/api/src/kyc/kyc.module.ts:
- Remove (or keep as fallback) the Sumsub module import.
- Import
YourVendorModule. - Update
KycServiceinjection.
If you went with Option B, register the strategy token here:
{
provide: 'KYC_PROVIDER',
useFactory: (config: ConfigService) =>
config.get('KYC_PROVIDER_NAME') === 'yourvendor' ? YourVendorService : SumsubService,
inject: [ConfigService],
}
8. Migration of in-flight applications [non-engineering]¶
Existing users with a Sumsub application id cannot magically appear in the new vendor's system. Decide:
- Hard cutover — new applications use the new vendor, old applications stay tracked under Sumsub but cannot progress (acceptable if Sumsub remains operational for read-only).
- Forced re-verification — every user re-submits documents to the new vendor. Communicate to users; estimate churn.
- Bulk export / import — request the new vendor support an import. Rare; expensive.
Hard cutover is the default. Document the decision in docs/api/changelog.md and brief support.
9. Tests¶
Required:
- Vendor signature verification: positive + negative.
- Verdict mapping: vendor verdict → internal verdict for every recognised verdict.
- Idempotent webhook (replaying the same callback does not double-update).
- Access-token endpoint returns the new vendor's payload shape.
10. Update the API surface SOT¶
Append docs/api/changelog.md entries:
- "Modified:
POST /kyc/access-tokenresponse shape changed from Sumsub's to vendor's." - "Modified: webhook payload."
- "Migration: in-flight Sumsub applications → policy decision (see step 8)."
This is potentially a breaking API change for any customer-side code that depends on the access-token shape. Communicate accordingly.
Verification¶
- Sandbox flow: sign up a fresh user, trigger KYC submission, watch the new vendor's webhook hit
/kyc/webhook, seeUser.kycLeveladvance. - Negative test: mutate the webhook payload — signature verify rejects with
401. - Replay: send the same approval webhook twice —
User.kycLeveladvances once. - Old user: an existing Sumsub user (pre-cutover) sees the new vendor's flow on next login (or the messaging chosen in step 8).
- Loki: search by
user_id— the full KYC handler chain is logged.
Notes¶
- The current
kyc.repository.tsis vendor-agnostic; you should not need to touch it (the schema lives in Prisma, not in the repository). - The Sumsub
WaitMutexpattern (insumsub.service.ts) for de-duplicating concurrent token requests is a per-vendor concern — re-implement only if the new vendor has rate limits that warrant it. - Don't delete the Sumsub module immediately; keep it for one release behind a feature flag in case rollback is needed.
Cross-links¶
integration-cookbook.md— index. KYC is recipe 6 / "very high friction".docs/api-reference/api.md→ tagsKYC API+KYC Admin API.docs/data-model/—User.kycLevel,KycVerificationVerdict,KycRejectType.docs/security-register.md— signature-verification requirements; update with the new vendor.docs/business/integration-options.md— what KYC swap involves commercially.docs/delivery/risks.md— KYC swap risk register.