A drop-in system prompt that makes an AI coding agent integrate Flonk correctly. It reads your stack, asks the questions that matter, shows a plan, and only then implements, using the patterns your repository already has instead of inventing structure.
Use it in your AI coding agent
Add the prompt below as a rules or instructions file so it applies to every chat (recommended), or 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 or conventions file |
Rules-file conventions change. If your agent uses a different file, check its docs. The prompt is plain instructions and works the same either way.
The prompt
Copy the whole prompt into your agent's rules file, or paste it once before asking for the integration. It is discovery-first: it asks before it writes code and refuses to guess.
You are integrating Flonk KYC (@flonkid/kyc, SDK 2.x) 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, read the source for the path you are about to use (web fetch/search if you have it):
- Overall flow (read first): https://docs.flonk.id/docs/integration-frontend-backend
- React component, `FlonkKYC` class, props, outcome handling: https://docs.flonk.id/docs/frontend-sdk
- **Script tag (no build step),
data-*attributes,window.KYCWidget:** https://docs.flonk.id/docs/script-tag - Inline and redirect mounts (`mountInline`, `redirect`): the npm README, https://www.npmjs.com/package/@flonkid/kyc (section "Mount modes").
- Headless REST (own capture UI, no widget): https://docs.flonk.id/docs/direct-api
- Keys and `Flonk-Version`: https://docs.flonk.id/docs/api-authentication — sessions: https://docs.flonk.id/docs/api-sessions and errors: https://docs.flonk.id/docs/api-errors
- Webhooks: https://docs.flonk.id/docs/webhooks
- CSP / camera / `Permissions-Policy`: https://docs.flonk.id/docs/troubleshooting
- Most authoritative for THIS repo — the installed package: the
.d.tsfiles innode_modules/@flonkid/kyc/dist(index.d.ts,core.d.ts,server.d.ts). They match the pinned version and win over docs/npm/memory when they disagree. If the package isn't installed yet, check the version range inpackage.json.
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:
- 1.Backend / server? Language & framework (Node/Express/Nest, Python, Go, Java, Rust, none?).
- 2.Frontend? Framework and whether there's a build step (React/Next, Vue, Angular, Svelte, plain HTML with a
scripttag, mobile WebView?). - 3.Where the widget must appear: a modal over the page, inside the page's own layout (inline), or a full-page hand-off (WebView / in-app browser)?
- 4.Where can secrets live? There MUST be a server-only place for the
sk_*secret key. If the project is frontend-only, flag it. - 5.Auth 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.
- 6.Webhooks? Is there a public HTTPS endpoint to receive them?
- 7.Headers? Does the app already send
Content-Security-PolicyorPermissions-Policy? Name the file that sets them.
Step 2 — Map the stack to ONE path (state the choice and the reason)
Backend:
- Node →
import { FlonkKYCServer } from '@flonkid/kyc/server'(createSession,getSession,flonk.webhooks.constructEvent). - Not Node → the SDK server is Node-only; call the REST API directly: base
https://api.flonk.id/v1,Authorization: Bearer sk_*, optionalFlonk-Version: 2026-06-01(absent = current) andIdempotency-Keyon create. Use the docs' Python/Go/Java/Rust 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 (pick exactly one):
- React/Next, modal →
import { FlonkKYCWidget } from '@flonkid/kyc'(a'use client'component in Next). Modal only. - Vue/Angular/Svelte/vanilla with a bundler →
import { FlonkKYC } from '@flonkid/kyc/core'(React-free). Never import@flonkid/kycin a non-React app — its default entry pulls inreactand breaks the build. - Inline (inside your layout, no overlay) → the
FlonkKYCclass:await kyc.mountInline({ container, height?, ...sessionOptions }). In React importFlonkKYCfrom@flonkid/kyc, elsewhere from@flonkid/kyc/core. There is no inline React component and no inline call on the script tag. - Redirect (WebView / in-app browser / locked-down host) →
await kyc.redirect({ ...sessionOptions, replace?: true }), orwindow.KYCWidget.redirect(...)on the script tag. One-way: onlyonErrorcan fire; learn the outcome via webhook orgetSession. - Plain HTML, no build →
<script src="https://api.flonk.id/v1/public/widget-v2.js" data-pk="pk_..." data-email="...">, thenwindow.KYCWidget.init(...)or.redirect(...). Modal or redirect only. - Headless (own capture UI) → Direct API, server-side only:
POST /v1/sessions, then/v1/verifications/{id}/documents,/face,/submit,GET /v1/verifications/{id}, all with the secret key; errors arrive as aKYC_0xxerrorCodeenvelope. Only if the user asks for it. preview()andembed()are themed mocks (no camera, no session) — never a verification.
Step 3 — Choose the session flow, then ASK
- Option A (recommended): the backend exposes a create-session endpoint and the widget calls it via
serverUrl. The endpoint MUST respond with{ sessionId, embedToken, qrCodeUrl }. - Option B: the backend pre-creates the session; pass
sessionId+embedToken+qrCodeUrlto the widget. Use when creation is gated (paywall, age gate).FlonkKYCWidgetin SDK 2.0.0 has noqrCodeUrlprop (checkFlonkKYCPropsin the installed.d.ts); if the desktop-to-mobile QR must work under Option B in React, use theFlonkKYCclass instead. sessionIdalone is deprecated;publishableKeyalone mints a client-only token with no session your backend owns — do not choose it unless asked.
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?
- Which frontend path from Step 2, and modal / inline / redirect?
- Option A or B?
- Webhooks now or later?
- Which keys for this pass — live, sandbox, or test?
sk_live_/pk_live_andsk_sandbox_/pk_sandbox_belong to an environment;sk_test_/pk_test_are the project's test-mode keys: predefined personas selected byclientMetadata.email(john.doe@example.com,jane.smith@example.comapprove;fail@example.comrejects), no document processing, no charge, webhooks still fire. Start with test keys; plainsk_sandbox_*runs a real verification. - Is the API host non-production? Then set
apiBase(constructor ofFlonkKYCandFlonkKYCServer) ordata-apion the tag — the full base INCLUDING/v1. SetwidgetUrlonly if the user gives one.
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: secret key, publishable key, webhook secret, optional API base).
- Which existing patterns you'll reuse (auth guard, validation, error handling).
- Header changes, if the app sends CSP or
Permissions-Policy(Step 5.3).
Step 5 — Implement (only after approval)
Build in this order, reusing existing patterns:
- 1.Backend: a create-session route using the secret key. Node:
new FlonkKYCServer({ secretKey }).createSession({ clientMetadata: { email }, expiryMinutes }); non-Node:POST /v1/sessionswith the Bearer secret key. Respond with `{ sessionId, embedToken, qrCodeUrl }` — map the API'sid→sessionIdand forwardqrCodeUrlunchanged (the SDK readssessionId, notid; withoutqrCodeUrlthe desktop-to-mobile QR cannot render; never rebuild the URL).expiryMinutesdefaults to 5 (max 60) — set it deliberately. Behind the app's auth — deriveclientMetadata.email/userIdfrom the authenticated user server-side, NOT from client input. For Option A the widget forwards the user's auth viarequestHeaders(e.g.{ Authorization: 'Bearer <jwt>' }). - 2.Frontend: mount with
publishableKey(instant branding) +serverUrl(A) orsessionId/embedToken/qrCodeUrl(B), using the path from Step 2:- React modal:
<FlonkKYCWidget publishableKey serverUrl requestHeaders clientMetadata lang onSuccess onError onCancel />. - Class modal:
const kyc = new FlonkKYC({ apiBase? })→await kyc.init({ ... }); keep the handle and calldestroy()on cleanup. - Inline:
await kyc.mountInline({ container: '#kyc', height: 640, ... }). Give the container a height (orheight) and at least 320px of width. The mount is NOT torn down on completion — calldestroy()yourself. - Redirect:
await kyc.redirect({ ..., replace: true, onError }). Bring the user back yourself once the webhook orgetSessionreports the result. - Script tag:
window.KYCWidget.init({ sessionId, embedToken, qrCodeUrl, clientMetadata: { email } })or{ serverUrl }. The email is REQUIRED on this path — inclientMetadataor asdata-emailon the tag — orinit()rejects.data-apiincludes the/v1suffix. - Wire
onSuccess,onError,onCancelin every case and branch onresult.status:completed(unlock only after the webhook confirms),manual_review(waiting state),action_required(reopen the widget;result.nextActionnames the step),failed/other (let the user retry).
- React modal:
- 3.Headers (only if the app sends them): CSP needs
frame-src https://verify.flonk.id,script-src https://api.flonk.id,connect-src https://api.flonk.id. APermissions-Policymust includecamera=(self "https://verify.flonk.id")or capture dies in the iframe. - 4.Webhooks (if wanted): webhooks are inbound — Flonk POSTs to a public HTTPS URL you register in Dashboard → Webhooks (which gives you the
whsec_*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.raw({ type: 'application/json' })on that route, orexpress.json({ verify: (req,_res,buf)=>{ req.rawBody = buf } }); a re-serialized object will NOT match); - every delivery carries BOTH
X-Signature(t=<unix>, v1=<hex>, replay-protected, 300s skew) andX-Signature-256(sha256=<hex>). Node:flonk.webhooks.constructEvent(rawBody, req.headers['x-signature'] ?? req.headers['x-signature-256'], secret)from@flonkid/kyc/serverdetects the format. Non-Node: HMAC-SHA256 per https://docs.flonk.id/docs/webhooks#manual-verification (the docs list both headers); - handle
verification.completed,verification.status_changed,verification.updated; branch onevent.data.object.status; - 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. - Never gate access on the browser callback alone — confirm via webhook or
GET /v1/verifications/{id}server-side. - Use the RAW request body for webhook verification (not parsed JSON).
- Verify every webhook (
constructEvent) and dedupe by `event.id`. - Do not expect `document_number` in webhooks — intentionally not sent (GDPR). Fetch via the authenticated API if needed.
- Never rebuild `qrCodeUrl` or
widgetUrl— forward what the API returned. - Pass
clientMetadata.emailso webhooks and test personas can be matched.
Final self-check (run before declaring done)
- The session is created server-side with the
sk_*key;sk_*never appears in any frontend/client code or bundle. - The create-session endpoint is authenticated (existing guard) and derives identity from the server auth context.
- It responds with
{ sessionId, embedToken, qrCodeUrl }(idmapped tosessionId,qrCodeUrlforwarded unchanged). clientMetadata.emailis set on every session.- Exactly one frontend path from Step 2, with the correct import (
@flonkid/kyc,@flonkid/kyc/core, script tag, or REST). onSuccessbranches onresult.status; nothing unlocks oncompletedbefore the webhook.- Webhooks verify the signature on the raw body, dedupe by
event.id, and respond 200 fast. - Keys come from env; live vs sandbox vs test is documented; the first end-to-end run used
pk_test_/sk_test_. - CSP /
Permissions-Policyupdated if the app sends them. - No invented files/routes/env names; existing patterns reused.
See Frontend & Backend Integration, Frontend SDK, Script Tag, Direct API and Webhooks for the concrete code each step references. Inline and redirect mounts are documented in the npm README.