A drop-in system prompt that makes an AI coding agent integrate Flonk correctly: it reads your stack, asks questions, and shows a plan first — then implements using your existing patterns, instead of guessing or inventing structure.
🤖 Use it in your AI coding agent
Add the prompt below as a rules / instructions file so it applies to every chat (recommended), or just paste it once before asking for the integration.
| Agent | Where to put it |
|---|---|
| Claude Code | CLAUDE.md at the repo root |
| Cursor | .cursor/rules/flonk.mdc (or legacy .cursorrules) |
| GitHub Copilot | .github/copilot-instructions.md |
| Windsurf | .windsurf/rules/flonk.md (or .windsurfrules) |
| OpenAI Codex | AGENTS.md at the repo root |
| Cline / Roo Code | .clinerules |
| Gemini CLI / Jules | GEMINI.md (or AGENTS.md) |
| Aider, Zed, JetBrains AI, others | paste as a system prompt / conventions file |
Rules-file conventions change — if your agent uses a different file, check its docs. The prompt works the same either way: it's plain instructions.
The prompt
Copy the whole thing into your agent's rules file (see the table above) — or paste it once before asking for the integration. It's plain instructions: discovery-first, asks before it writes code, and refuses to guess.
You are integrating Flonk KYC (@flonkid/kyc) into THIS repository. Follow these steps IN ORDER. Do NOT write integration code until Step 5 (after the plan is approved).
Ground rules (apply throughout)
- Do not invent anything. No made-up file paths, folders, route names, middleware names, env var names, or framework conventions. Use ONLY structures that already exist in this repo. If something doesn't exist, ask — don't assume.
- Cite your evidence. Every conclusion must reference the exact file(s) you inspected (e.g.
Backend: NestJS (apps/api/src/main.ts)). - Reuse, don't introduce. Match the existing route structure, validation library, auth middleware, DI pattern, error handling, and HTTP client. Do NOT add a new architectural pattern (no Zod in a Joi project, no Express Router in a Nest app, no axios where the code uses fetch).
- If anything is ambiguous, ASK and WAIT — do not guess.
Step 0 — Ground yourself in the official sources (don't rely on memory)
The SDK evolves; your training data may be stale. Before assuming any API, consult the authoritative sources (use web fetch/search if you have it):
- Docs: https://docs.flonk.id — esp. Frontend SDK, Frontend ↔ Backend Integration, Webhooks, Errors, and the API reference pages.
- npm: https://www.npmjs.com/package/@flonkid/kyc — the README and the latest published version.
- Most authoritative for THIS repo — the installed package: read the actual types and exports in
node_modules/@flonkid/kyc(its.d.tsfiles). These match the version this project pins, so they win over docs/npm/memory when they disagree. If the package isn't installed yet, check the version range inpackage.json.
Prefer these sources over recollection. If you cannot reach the web AND the package isn't installed, say so and ask the user to paste the relevant docs.
Step 1 — Discover the stack (read the repo; cite files)
Determine and state, each with the file(s) you inspected:
- 1Backend / server? Language & framework (Node/Express/Nest, Python, Go, Java, Rust, none?).
- 2Frontend? Framework and whether there's a build step (React/Next, Vue, Svelte, plain HTML with a
scripttag, mobile webview?). - 3Where can secrets live? There MUST be a server-only place for the
sk_*secret key. If the project is frontend-only, flag it. - 4Auth model? How does the app authenticate its OWN end users (JWT, session cookie, OAuth, API key, none)? Name the existing guard/middleware — the create-session route MUST sit behind it.
- 5Webhooks? Is there a public HTTPS endpoint to receive them?
Step 2 — Decide SDK fit (pick the path, state the reason)
- Backend = Node →
@flonkid/kyc/server(createSession + webhook verify). - Backend ≠ Node → the SDK server is Node-only; use the REST API directly (
Authorization: Bearer sk_*) with the docs' language snippets. - No backend at all → STOP: a server is required to hold the secret key and create sessions. Offer the minimal endpoint as the fix.
- Frontend = React/Next →
FlonkKYCWidgetfrom@flonkid/kyc. - Frontend = other framework / has a build (Vue, Angular, Svelte, vanilla-TS) →
FlonkKYCclass imported from `@flonkid/kyc/core` (the React-free entry). Do NOT import from@flonkid/kycin a non-React app — its default entry pulls inreactand breaks the bundler build. - Frontend = plain HTML, no build → the classic
widget.jsscript tag.
Step 3 — Choose the session flow, then ASK
- Option A (recommended): backend exposes a create-session endpoint; the widget calls it via
serverUrl. - Option B: backend pre-creates the session; pass
sessionId+embedTokento the widget. Use when creation is gated (paywall, age gate).
Ask, and WAIT for answers:
- Confirmed backend framework + which file the create-session route goes in?
- Which existing auth guard protects it, and how do you read the current user's identity (email/userId) on the server?
- React or non-React frontend?
- Option A or B?
- Webhooks now or later?
- Which keys for this pass — live, sandbox, or test? Test keys (secret starts with
sk_test_, e.g.sk_test_sandbox_*+pk_test_*) run test mode: mock data, skips real AI/camera, webhooks still fire. Use them for a demo; plainsk_sandbox_*runs a real verification.
Step 4 — Plan (show before coding; WAIT for approval)
Present, and do NOT proceed until the user approves:
- Files to modify (exact existing paths).
- New files to create (only if no existing home fits — justify each).
- Integration flow (request → session → widget → webhook), in one short list.
- Env vars to add (names + purpose).
- Which existing patterns you'll reuse (auth guard, validation, error handling).
Step 5 — Implement (only after approval)
Build in this order, reusing existing patterns:
- 1Backend: a create-session route using the secret key (Node:
new FlonkKYCServer({ secretKey }).createSession({ clientMetadata: { email } }); non-Node: POST/v1/sessionswith the Bearer secret key). Respond with `{ sessionId, embedToken, qrCodeUrl }` — map the API'sid→sessionId, and forwardqrCodeUrl(without it the desktop→mobile QR can't render). The raw session object'sidis NOT read. Behind the app's auth — deriveclientMetadata.email/userIdfrom the authenticated user server-side, NOT from client input. For theserverUrlflow, forward the user's auth viarequestHeaders(e.g.Authorization: Bearer <jwt>). - 2Frontend: mount the widget with
publishableKey(instant branding) +serverUrl(A) orsessionId/embedToken(B), using the import you chose in Step 2:- React/Next:
import { FlonkKYCWidget } from '@flonkid/kyc'(in Next, a'use client'component). - Vue/Angular/Svelte/vanilla:
import { FlonkKYC } from '@flonkid/kyc/core'(the React-free entry) →new FlonkKYC().init({...}). Do NOT import from@flonkid/kychere — it pulls inreactand breaks the bundler build. - No build step:
<script src="https://widget.flonk.id/v1/widget.js" data-api="https://api.flonk.id">, thenwindow.KYCWidget.init({ sessionId, embedToken })(or{ publishableKey }).
- React/Next:
Wire onSuccess/onError/onCancel in every case.
- 1Webhooks (if wanted): webhooks are inbound — Flonk POSTs to a public HTTPS URL you register in Dashboard → Webhooks (which gives you the webhook secret).
localhostcan't receive them; for local testing flag that a tunnel (e.g.ngrok http <port>) is needed and the tunnel URL must be set in the dashboard. Then:- capture the RAW request body (most JSON parsers consume it — e.g. with Express,
express.json({ verify: (req,_res,buf)=>{ req.rawBody = buf } }), then verifyreq.rawBody; a re-serialized object will NOT match); - verify with
flonk.webhooks.constructEvent(rawBody, header, secret)(header =X-Signature, falling back toX-Signature-256); - dedupe by `event.id` (unique index / Redis
SET NX) before processing — delivery is at-least-once; - respond
200quickly; do heavy work async.
- capture the RAW request body (most JSON parsers consume it — e.g. with Express,
HARD RULES — never violate
- NEVER put the
sk_*secret key in frontend/client code or a browser bundle. Frontend uses thepk_*publishable key only. - The create-session endpoint MUST be authenticated (behind the app's own auth). Never expose it unauthenticated — each session costs money and binds a verification to a user. Derive the user identity server-side.
- Do not invent file paths/route/middleware/env names — use what exists.
- Reuse existing patterns — don't introduce new libraries or architectures.
- Non-React frontend → import from `@flonkid/kyc/core`, never
@flonkid/kyc. The default entry pulls inreactand breaks the production build in Vue/Angular/Svelte/vanilla projects. - Use the RAW request body for webhook verification (not parsed JSON).
- Verify every webhook (
constructEvent) and dedupe by `event.id` — delivery is at-least-once. - Do not expect `document_number` in webhooks — intentionally not sent (GDPR). Fetch via the authenticated API if needed.
- Prefer the `X-Signature` header (replay-protected) when verifying manually.
- Pass
clientMetadata.emailso webhooks can be matched to your users.
Final self-check (run before declaring done)
- ☐
sk_*never appears in any frontend/client code or bundle. - ☐The create-session endpoint is authenticated (existing guard).
- ☐It responds with
{ sessionId, embedToken, qrCodeUrl }(theidfield is mapped tosessionId). - ☐User identity comes from the server auth context, not client input.
- ☐Webhooks verify the signature and use the raw body.
- ☐Webhook processing is idempotent (deduped by
event.id). - ☐No invented files/routes/env names; existing patterns reused.
- ☐All new env vars are documented.
See Frontend SDK, Frontend ↔ Backend Integration, and Webhooks for the concrete code each step references.