Codego Developers · Card · Visa/Mastercard Issuing · v1.0

Visa & Mastercard Card Issuing API Reference

Issue USD-denominated Visa & Mastercard cards under your brand. Onboard cardholders with KYC/KYB, issue virtual & physical cards, fund the credit line with USDC, manage PINs & limits, and receive HMAC-signed webhooks on every event.

Sandbox  https://vcc-sandbox.codegotech.com/api/v1
Live      https://visacard.codegotech.com/api/v1
Auth  API key (X-Api-Key)  ·  Body application/json

This reference covers the API integration for the Visa/Mastercard card issuing programme. If your programme runs in "Core banking Included" mode (a fully managed, branded cardholder portal that Codego hosts for you) there is nothing to integrate here — your cardholders use the portal directly and you manage everything from your whitelabel console.

Quickstart — Sandbox onboarding

Self-serve sandbox access in three steps. You'll get a whitelabel id and a sandbox API key (vcck_sbx_…) by email within minutes. Sandbox runs on a fully isolated database — your test calls never touch the live ledger.

1 · Sign up — get your sandbox API key

Open apikey-visacard-sandbox.codegotech.com and submit the short signup form (name, email, phone, company, website). Within ~30 seconds you'll receive an email containing:

  • The sandbox dashboard URL at whitelabel-sandbox.codegotech.com
  • Login email + password
  • Your whitelabel id
  • Your API key in the form vcck_sbx_… — shown only once, store it safely
  • A ready-to-paste curl snippet + link to this reference
Sandbox provisioning is fully automated and instant — no approval needed. The dashboard lets you inspect cardholders, cards, transactions and KYC sessions you create via the API.

2 · Open a KYC session for your first cardholder

Every cardholder onboarding starts with a KYC session. You call it from your backend, pass your own externalUserId, and get back a one-time iframeUrl. You drop that URL into an iframe in your app — the cardholder uses it to capture their ID, take a selfie, verify their address. Documents never leave Codego. When the cardholder finishes, you receive a webhook with the KYC outcome and the resolved profile.

curl -X POST https://kyc-sandbox.codegotech.com/api/session/create \ -H 'X-API-Key: your_kyc_sandbox_key' \ -H 'Content-Type: application/json' \ -d '{ "externalUserId": "your-user-001", "applicantType": "individual", "email": "[email protected]", "origin": "https://app.your-brand.com", "locale": "en", "returnUrl": "https://app.your-brand.com/onboarding/done" }'

Expected response (201 Created):

{ "sessionId": "01HXY3FZK7QM2N8RP6V4XBJDGC", "iframeUrl": "https://kyc-sandbox.codegotech.com/embed?sid=01HXY3FZK7QM2N8RP6V4XBJDGC&t=eyJ…", "expiresAt": "2026-05-25T18:42:00Z" }

Note — no userId here, and that's correct. At session-create time the cardholder hasn't been verified yet, so no user exists. The userId you use for every later call (GET /users/{userId}, POST /users/{userId}/cards, …) is delivered in the user.updated webhook once KYC resolves — see KYC outcomes below. Keep your own externalUserId as the correlation key until then.

Sandbox shortcut: every KYC outcome in sandbox is auto-approved — the iframe accepts any sample image and resolves the session in seconds. Real KYC review (production) can take 1–2 business days.

Then embed the iframe in your app and listen for completion:

<iframe src={iframeUrl} style={{ width:'100%', height:780 }} allow="camera" /> window.addEventListener('message', e => { if (e.origin !== 'https://kyc-sandbox.codegotech.com') return; if (e.data?.type === 'kyc:done') { // KYC submitted — wait for the user.updated webhook for the final outcome. } });

Integration flow

A complete integration follows six sequential steps. The endpoints in the left navigation are grouped accordingly. Cardholder onboarding is iframe-only by design: your end-user captures their ID and selfie directly inside the Codego KYC iframe — you never receive, store or forward identity documents, which keeps you out of the regulated data-processor scope. You receive only the final structured result via webhook.

stepWhat happensEndpoints involved
1 · Onboard Your backend opens a KYC session with your own externalUserId and gets a one-time iframeUrl. For an individual the iframe captures ID + selfie + address proof; for a company (KYB) the same iframe also collects legal entity info, UBOs and incorporation documents. Codego runs identity review and creates the on-chain collateral contract on approval. You receive the outcome via the user.updated (individual) or company.updated (KYB) webhook. POST /api/session/create { applicantType: individual|company } · <iframe> embed · GET /applications/{userId} or GET /applications/companies/{companyId}
2 · Fund Fund the card's USDC collateral. Direct — send USDC/USDT on Base or Arbitrum straight to the collateral contract (instant). Multi-coin top-up — list the supported coins, get a persistent deposit address for any coin/network, and Codego auto-converts to USDC and credits the card in a few minutes. Monitor status + balance. See Fund the card. GET /users/{userId}/contracts · GET /users/{userId}/balances · GET /users/{userId}/topup/assets · POST /users/{userId}/topup/address · GET /users/{userId}/topup/orders
3 · Issue Issue a virtual or physical Visa card to an approved cardholder, with per-frequency spending limits. POST /users/{userId}/cards · GET /cards · GET /cards/{cardId}
4 · Manage Lock / unlock / cancel cards; adjust limits; render the PAN, CVC or PIN under the encrypted-secrets flow. PATCH /cards/{cardId} · GET /cards/{cardId}/secrets · GET /cards/{cardId}/pin
5 · Transactions List and inspect authorisations, captures and settled spend. Open a dispute if needed. GET /transactions · GET /transactions/{txId} · POST /transactions/{txId}/disputes
6 · Webhooks Receive real-time notifications for every state change — KYC outcome, contract creation, card status, authorisations, settlements, disputes. Configured in the partner dashboard. HMAC-signed deliveries.
Sandbox parity: every endpoint, request shape, response shape, error code and webhook event is identical between sandbox and live. To promote your integration, change only the base URL host and the API key prefix. KYC outcomes in sandbox are auto-decided (always approved) so you can exercise the full lifecycle without waiting for human review.
Regulatory notice: issuance and KYC are subject to the rules of the underlying issuing programme and applicable AML/CTF regulation. Sandbox is for technical integration only — no real funds, no real identity verification, and no card may be presented for payment.

Authentication

Every request is authenticated with your whitelabel API key in the X-Api-Key header. Keys look like vcck_sbx_… (sandbox) or vcck_live_… (production) and are issued at onboarding. Keep the key server-side — never expose it in client code.

Each key is scoped to one whitelabel tenant; all resources you create or read are isolated to it. Sandbox and live are fully separated. To rotate a key, contact [email protected].

Use one key for the whole flow. A cardholder belongs to the tenant whose key created it — including the key used to open the KYC iframe session (POST /api/session/create). Every userId you pass to POST /users/{userId}/cards, GET /users/{userId}, /applications/{userId}, etc. must belong to your tenant. Operating on a card or user that belongs to a different key returns 404 (card / user not found), and GET /cards & GET /transactions only ever return resources owned by the calling key. Mixing keys across KYC and card calls is the most common cause of an unexpectedly empty list or a 404.
Rate limit: 1,000 requests / minute per key. Responses include X-RateLimit-Remaining and X-RateLimit-Reset. On 429, back off until the reset timestamp.
Card PAN / CVC / PIN are PCI-sensitive: retrieved only via the encrypted-secrets endpoints with an RSA-OAEP SessionId header, and must never be stored.

Environments — sandbox & live

environmentBase URLKey prefixNotes
Sandboxhttps://vcc-sandbox.codegotech.com/api/v1vcck_sbx_…Test cards, simulated balances. KYC instant-approval shortcut: set lastName to TestApproved on a consumer application.
Livehttps://visacard.codegotech.com/api/v1vcck_live_…Real Visa rails, real cards, real KYC. Authorisation required — contact [email protected] to enable.

Both environments share the same endpoint paths, request and response shapes — the same code works in both. Switch hosts, switch keys, and you're done. Webhooks are configured separately per environment.

Going live: when your sandbox integration is ready, email [email protected] with your whitelabel id. We'll verify your compliance setup and issue your vcck_live_… key. To activate: 1) switch the base URL to https://visacard.codegotech.com/api/v1, 2) swap the sandbox key for your live key, 3) send us the server IP(s) that will call the live API — live access is IP-allowlisted (default-deny), so calls from non-allowlisted IPs are rejected, 4) reconfigure your webhook URL for the live environment.

Fund the card — deposits & multi-coin top-up

A card spends against its USDC collateral. You fund it in one of two ways. Direct: send USDC (or USDT) on Base or Arbitrum straight to the cardholder's collateral contract — instant, no fee. Multi-coin top-up: let the cardholder deposit any supported coin on any supported network (e.g. USDT-TRON, ETH, USDC on Ethereum) to a persistent address — Codego auto-converts it to USDC and credits the card in a few minutes, minus a small top-up fee. No amount needs to be declared up front: the cardholder simply sends to the address and the deposit is detected and credited automatically.

Data-driven — new coins need no code change. The list of supported coins/networks, their availability and fees are returned by GET /users/{userId}/topup/assets. Build your UI from that response and any coin we add later appears automatically — nothing to redeploy on your side.
Multi-coin top-up must be enabled for your tenant. Until it is, the top-up endpoints return 403 (top-up not enabled) and you fund cards with the Direct method only. To enable it, email [email protected]. userId is the cardholder id delivered in the user.updated webhook and must belong to your API key's tenant.

Direct funding (instant)

Fetch the collateral contract for the chain you want and send USDC/USDT to that address on that network. The card balance updates when the deposit confirms on-chain (a transaction webhook is delivered). Supported direct networks: Base (chainId 8453) and Arbitrum One (chainId 42161).

GET /users/{userId}/contracts
GET /users/{userId}/balances

1 · List supported coins & networks

Returns every coin/network the cardholder can deposit, with per-network mode, speed, fee and live availability. Render your deposit UI directly from this.

GET /users/{userId}/topup/assets

// 200 OK
{
  "assets": [
    {
      "coin": "USDC",
      "name": "USD Coin",
      "logoUrl": "https://.../usdc.png",
      "networks": [
        { "network": "arbitrum", "label": "Arbitrum One", "mode": "direct",
          "speed": "instant", "feePct": 0, "feeFixedUsd": 0, "feeNetworkUsd": 0,
          "available": true, "reason": null },
        { "network": "ethereum", "label": "Ethereum (ERC-20)", "mode": "convert",
          "speed": "few_minutes", "feePct": 0.5, "feeFixedUsd": 0, "feeNetworkUsd": 3,
          "available": true, "reason": null }
      ]
    },
    { "coin": "USDT", "name": "Tether", "logoUrl": "...", "networks": [ /* … */ ] },
    { "coin": "ETH",  "name": "Ethereum", "logoUrl": "...", "networks": [ /* … */ ] }
  ]
}
FieldMeaning
modedirect = send to the collateral contract (instant, no fee). convert = send to the top-up address; auto-converted to USDC.
speedinstant or few_minutes — show it as the ETA badge.
feePctTop-up fee percentage applied to the deposit value (0 on direct networks).
feeNetworkUsdFixed network/swap fee in USD added on top of the percentage (e.g. 3 for USDC/USDT on Ethereum). 0 on direct networks.
available / reasonWhen available:false, grey the network out and show reason (e.g. network_temporarily_unavailable).

2 · Get the deposit address

Returns the persistent address for that coin/network (the same address is reused for every future deposit). For a direct network the response tells you to use the collateral contract instead.

POST /users/{userId}/topup/address
{ "coin": "USDT", "network": "tron" }

// 200 OK (convert network)
{ "mode": "convert", "coin": "USDT", "network": "tron",
  "depositAddress": "T…", "persistent": true }

// 200 OK (direct network → use the collateral contract)
{ "mode": "direct", "coin": "USDC", "network": "arbitrum", "useExistingDeposit": true }
Network safety. Show a prominent warning: the cardholder must send only that coin on that exact network to the address. Sending another asset or using another network can permanently lose the funds.

3 · (Optional) Quote the net amount

Preview how much USDC a deposit would credit. Optional — the flow works without it (the deposit is credited automatically). For volatile coins the amount is an estimate (the real credit is the amount realised from the conversion).

POST /users/{userId}/topup/quote
{ "coin": "USDC", "network": "ethereum", "amount": 100 }

// 200 OK
{ "coin": "USDC", "network": "ethereum",
  "grossAmount": 100, "feePct": 0.5, "feeNetworkUsd": 3,
  "feeAmount": 3.5, "netUsdc": 96.5,
  "speed": "few_minutes", "needsPrice": false }
// volatile coins (e.g. ETH) also return: "priceUsd", "grossUsd", "estimate": true

4 · Track the deposit

Poll the cardholder's deposits and their live status. Statuses are neutral (no internal mechanics exposed). The card balance also updates via GET /users/{userId}/balances once credited.

GET /users/{userId}/topup/orders?limit=20

// 200 OK
{
  "orders": [
    {
      "orderRef": "top_…",
      "coin": "USDT", "network": "tron",
      "amountIn": 50, "creditedUsdc": 46.75,
      "feePctUsd": 0.25, "feeGasUsd": 0, "feeTotalUsd": 3.25,
      "status": "completed",          // processing | completed | below_minimum | failed | expired
      "progressLabel": "Completed",   // Detected | Processing | Almost done | Finalizing | Completed
      "etaSec": 0, "step": 4, "steps": 4,
      "detectedAt": "…", "creditedAt": "…", "createdAt": "…"
    }
  ]
}

// Single deposit: GET /users/{userId}/topup/orders/{orderRef}
Sandbox parity: same paths, request/response shapes and statuses in sandbox and live — switch host + key only.

KYC SDK — drop-in iframe

If you don't want to build the document-capture UI yourself, embed the Codego KYC iframe in your app. The wizard handles ID capture, liveness, address proof, UBOs (for KYB) and (for selfies) anti-AI flash + zoom challenges — entirely on Codego infrastructure. Documents never touch your servers.

Two steps: (1) POST to https://kyc-sandbox.codegotech.com/api/session/create from your backend with your KYC sandbox key → you get an iframeUrl. (2) Drop the URL into an <iframe allow="camera"> in your app and listen for postMessage on completion. The session forwards the result to the card issuing API automatically.

The KYC sandbox key is issued together with your card issuing sandbox key — same email, same onboarding step. See the KYC SDK section below for the full request/response shape.

KYC outcomes — the user.updated webhook

After the cardholder submits, Codego runs identity review and notifies you with a user.updated webhook. This webhook is where you receive the userId (field body.id) used for all later /users/{userId}/* calls, and it echoes back your externalUserId so you can match it to your own user record. The body.applicationStatus field carries the outcome. Handle these values:

applicationStatusMeaning & what to do
approvedIdentity verified. The on-chain collateral contract is created — you can now issue a card.
needsVerificationTransient — review still in progress. No action needed; a follow-up webhook will carry the final outcome.
manualReview / pendingUnder human review (production can take 1–2 business days). No action needed.
needsInformationAction required. The cardholder must fix one specific thing — the reason is in body.applicationReason (e.g. BAD_SELFIE). Re-open the verification (see below) — the iframe will show only the step that needs correcting, not the whole flow.
deniedVerification could not be completed. body.applicationReason gives the reason.

Example user.updated payload (HMAC-signed, delivered to your configured webhook URL):

{ "type": "user.updated", "body": { "id": "0a21924e-56fe-4a2c-a4f1-8c00b1ef6927", // ← the userId — use it in /users/{userId}/* "externalUserId": "your-user-001", // ← the value you sent to /api/session/create "applicationStatus": "approved", "kycStatus": "approved", "createdAt": "2026-05-25T18:45:10Z" } }

Map body.id → your record keyed by body.externalUserId, then continue: GET /users/{userId}, fund the contract, and POST /users/{userId}/cards.

Configuring your webhook (same in sandbox & live). Set your receiving URL in your whitelabel console — sandbox: vcc-whitelabel-sandbox.codegotech.comAPI & WebhooksWebhook endpoint; live: vcc-whitelabel.codegotech.com. On first save the console shows your signing secret. Every delivery is a POST with header Signature: sha256=HMAC_SHA256(rawBody, secret) and an Idempotency-Key header. Verify the signature against the raw body before trusting the event, and de-dupe on the idempotency key. Sandbox fires the exact same events (KYC auto-approves in seconds) so you can test the full flow as if live.
// Verify a delivery (Node)
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', WEBHOOK_SECRET)
  .update(rawBody)            // the raw request body, not re-serialised JSON
  .digest('hex');
if (req.headers['signature'] !== expected) return res.status(401).end();

Resuming a needsInformation session. Call POST /api/session/create again, passing resumeSessionId set to the original sessionId instead of starting a fresh session. You get back a new iframeUrl that re-opens the same application at exactly the step flagged in review (e.g. just the selfie/liveness). The cardholder re-submits only that document — no new application is created, no data re-entered. On success the status returns to submitted (back in review).

// On a needsInformation webhook, re-open the SAME session — fix only what's needed.
POST https://kyc-sandbox.codegotech.com/api/session/create
{ "resumeSessionId": "<original sessionId>" }
// → { "iframeUrl": "…?resume=selfie", "resume": "selfie", "sessionId": "<same id>" }
Sandbox note: KYC is auto-approved in sandbox, so you'll normally see approved directly. The needsInformation path is exercised in production (or by submitting a deliberately unmatchable selfie).

Errors

The API uses standard HTTP status codes. 2xx = success; 4xx = client error with a JSON { error, message } body; 5xx = transient — retry with backoff.

codeMeaning
200 / 201Success
400Validation failed — see message
401Missing or invalid API key
404Resource not found
429Rate limit exceeded — honour X-RateLimit-Reset