AI Integration Prompt

Drop-in system prompt for AI coding agents (Claude Code, Cursor, Codex, Windsurf, Copilot, …) to integrate Flonk KYC — discovery-first, with hard rules.

2 min read

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.

AgentWhere to put it
Claude CodeCLAUDE.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 CodexAGENTS.md at the repo root
Cline / Roo Code.clinerules
Gemini CLI / JulesGEMINI.md (or AGENTS.md)
Aider, Zed, JetBrains AI, otherspaste 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.

flonk-kyc-integration.prompt

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):

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. 1.Backend / server? Language & framework (Node/Express/Nest, Python, Go, Java, Rust, none?).
  2. 2.Frontend? Framework and whether there's a build step (React/Next, Vue, Angular, Svelte, plain HTML with a script tag, mobile WebView?).
  3. 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. 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. 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. 6.Webhooks? Is there a public HTTPS endpoint to receive them?
  7. 7.Headers? Does the app already send Content-Security-Policy or Permissions-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_*, optional Flonk-Version: 2026-06-01 (absent = current) and Idempotency-Key on 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/kyc in a non-React app — its default entry pulls in react and breaks the build.
  • Inline (inside your layout, no overlay) → the FlonkKYC class: await kyc.mountInline({ container, height?, ...sessionOptions }). In React import FlonkKYC from @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 }), or window.KYCWidget.redirect(...) on the script tag. One-way: only onError can fire; learn the outcome via webhook or getSession.
  • Plain HTML, no build → <script src="https://api.flonk.id/v1/public/widget-v2.js" data-pk="pk_..." data-email="...">, then window.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 a KYC_0xx errorCode envelope. Only if the user asks for it.
  • preview() and embed() 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 + qrCodeUrl to the widget. Use when creation is gated (paywall, age gate). FlonkKYCWidget in SDK 2.0.0 has no qrCodeUrl prop (check FlonkKYCProps in the installed .d.ts); if the desktop-to-mobile QR must work under Option B in React, use the FlonkKYC class instead.
  • sessionId alone is deprecated; publishableKey alone 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_ and sk_sandbox_/pk_sandbox_ belong to an environment; sk_test_/pk_test_ are the project's test-mode keys: predefined personas selected by clientMetadata.email (john.doe@example.com, jane.smith@example.com approve; fail@example.com rejects), no document processing, no charge, webhooks still fire. Start with test keys; plain sk_sandbox_* runs a real verification.
  • Is the API host non-production? Then set apiBase (constructor of FlonkKYC and FlonkKYCServer) or data-api on the tag — the full base INCLUDING /v1. Set widgetUrl only 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. 1.Backend: a create-session route using the secret key. Node: new FlonkKYCServer({ secretKey }).createSession({ clientMetadata: { email }, expiryMinutes }); non-Node: POST /v1/sessions with the Bearer secret key. Respond with `{ sessionId, embedToken, qrCodeUrl }` — map the API's id → sessionId and forward qrCodeUrl unchanged (the SDK reads sessionId, not id; without qrCodeUrl the desktop-to-mobile QR cannot render; never rebuild the URL). expiryMinutes defaults to 5 (max 60) — set it deliberately. Behind the app's auth — derive clientMetadata.email/userId from the authenticated user server-side, NOT from client input. For Option A the widget forwards the user's auth via requestHeaders (e.g. { Authorization: 'Bearer <jwt>' }).
  2. 2.Frontend: mount with publishableKey (instant branding) + serverUrl (A) or sessionId/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 call destroy() on cleanup.
    • Inline: await kyc.mountInline({ container: '#kyc', height: 640, ... }). Give the container a height (or height) and at least 320px of width. The mount is NOT torn down on completion — call destroy() yourself.
    • Redirect: await kyc.redirect({ ..., replace: true, onError }). Bring the user back yourself once the webhook or getSession reports the result.
    • Script tag: window.KYCWidget.init({ sessionId, embedToken, qrCodeUrl, clientMetadata: { email } }) or { serverUrl }. The email is REQUIRED on this path — in clientMetadata or as data-email on the tag — or init() rejects. data-api includes the /v1 suffix.
    • Wire onSuccess, onError, onCancel in every case and branch on result.status: completed (unlock only after the webhook confirms), manual_review (waiting state), action_required (reopen the widget; result.nextAction names the step), failed/other (let the user retry).
  3. 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. A Permissions-Policy must include camera=(self "https://verify.flonk.id") or capture dies in the iframe.
  4. 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). localhost can'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, or express.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) and X-Signature-256 (sha256=<hex>). Node: flonk.webhooks.constructEvent(rawBody, req.headers['x-signature'] ?? req.headers['x-signature-256'], secret) from @flonkid/kyc/server detects 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 on event.data.object.status;
    • dedupe by `event.id` (unique index / Redis SET NX) before processing — delivery is at-least-once;
    • respond 200 quickly; do heavy work async.

HARD RULES — never violate

  • NEVER put the sk_* secret key in frontend/client code or a browser bundle. Frontend uses the pk_* 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.email so 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 } (id mapped to sessionId, qrCodeUrl forwarded unchanged).
  • clientMetadata.email is set on every session.
  • Exactly one frontend path from Step 2, with the correct import (@flonkid/kyc, @flonkid/kyc/core, script tag, or REST).
  • onSuccess branches on result.status; nothing unlocks on completed before 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-Policy updated 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.