Bank-grade encryption No KYC to buy Instant virtual cards
Early access: Gpaynow Card API v2 is in development and no card issuer is connected yet. Join the waitlist; no payment is taken until your access is approved. Request access
API reference · v2 early access

Gpaynow Card API documentation

Issue and manage prepaid virtual Visa and Mastercard cards for your users through one REST API. Create cardholders, issue cards, load and withdraw funds, control spending and receive real-time events.

Card issuingPlanned: cards issued on Visa and Mastercard by a licensed card-issuing partner. No issuer is connected yet (early access). Gpaynow is not the card issuer.
Prepaid balanceYou fund your Gpaynow balance with stablecoins; card loads and fees are paid from it.
Test modeBuild with test keys first: instant KYC approval and simulated funds, no real money.
WebhooksSigned events for issuing, funding, transactions, KYC and 3-D Secure codes.

Base URL: https://gpaynow.net/api/v2 — all requests use HTTPS and JSON.

How it works

  1. Get API access. Request early access to receive test keys. Live keys are issued after business verification (KYB).
  2. Fund your balance. Add USD to your Gpaynow balance with any stablecoin (USDT, USDC and more), $1.00 flat per top-up. Check it with GET /balance.
  3. Create a cardholder. Every card belongs to a verified person. Identity verification (KYC) runs on the cardholder before cards can be issued.
  4. Issue a card with an initial load. The load and the issue fee are taken from your balance.
  5. Manage the card: fund, unload, set limits, freeze, terminate, read transactions — and react to webhooks.
Money flow: your Gpaynow balance → card balance (on fund / issue) → merchant (on purchase). Unloads and terminations return money to your Gpaynow balance.

Quickstart

Issue your first test card in four requests. Replace gpn_test_xxx with your test key.

1. Check your key

curlcurl https://gpaynow.net/api/v2/ping \
  -H "Authorization: Bearer gpn_test_xxx"

2. Create a cardholder

curlcurl https://gpaynow.net/api/v2/cardholders \
  -H "Authorization: Bearer gpn_test_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7b1c-cardholder-001" \
  -d '{
    "first_name": "Amina",
    "last_name": "Okafor",
    "email": "amina@example.com",
    "date_of_birth": "1994-03-12",
    "nationality": "NG",
    "address": { "line1": "12 Allen Ave", "city": "Lagos", "postal_code": "100001", "country": "NG" },
    "external_id": "user_8841"
  }'

3. Pick a product

curlcurl https://gpaynow.net/api/v2/products -H "Authorization: Bearer gpn_test_xxx"

4. Issue a card with $50

curlcurl https://gpaynow.net/api/v2/cards \
  -H "Authorization: Bearer gpn_test_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7b1c-card-001" \
  -d '{ "cardholder_id": "ch_9LqT2", "product_id": "prd_visa_us_01", "amount": 50.00, "name_on_card": "AMINA OKAFOR" }'

Same flow in code

Node.jsconst API = "https://gpaynow.net/api/v2";
const headers = { Authorization: `Bearer ${process.env.GPAYNOW_KEY}`, "Content-Type": "application/json" };

const ch = await fetch(`${API}/cardholders`, { method: "POST", headers: { ...headers, "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({ first_name: "Amina", last_name: "Okafor", email: "amina@example.com", date_of_birth: "1994-03-12",
    nationality: "NG", address: { line1: "12 Allen Ave", city: "Lagos", postal_code: "100001", country: "NG" } }) }).then(r => r.json());

const card = await fetch(`${API}/cards`, { method: "POST", headers: { ...headers, "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({ cardholder_id: ch.data.id, product_id: "prd_visa_us_01", amount: 50 }) }).then(r => r.json());
console.log(card.data.id, card.data.last4, card.data.status);
PHP$api = 'https://gpaynow.net/api/v2';
$send = function (string $path, array $body) use ($api) {
    $ch = curl_init($api . $path);
    curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('GPAYNOW_KEY'), 'Content-Type: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16))], CURLOPT_POSTFIELDS => json_encode($body)]);
    return json_decode(curl_exec($ch), true);
};
$card = $send('/cards', ['cardholder_id' => 'ch_9LqT2', 'product_id' => 'prd_visa_us_01', 'amount' => 50]);
echo $card['data']['id'];
Pythonimport os, uuid, requests
API = "https://gpaynow.net/api/v2"
H = {"Authorization": f"Bearer {os.environ['GPAYNOW_KEY']}"}
card = requests.post(f"{API}/cards", headers={**H, "Idempotency-Key": str(uuid.uuid4())},
                     json={"cardholder_id": "ch_9LqT2", "product_id": "prd_visa_us_01", "amount": 50}).json()
print(card["data"]["id"], card["data"]["status"])

Authentication

Send your API key as a Bearer token on every request. Keys are created in your Gpaynow dashboard under API → Keys and are shown only once.

headerAuthorization: Bearer gpn_live_4f8a…
Key prefixMode
gpn_test_Test mode — simulated money and instant KYC
gpn_live_Live mode — real cards and real money (after KYB approval)

Scopes

ScopeAllows
cardholders:read / cardholders:writeRead / create and change cardholders
cards:read / cards:writeRead / issue, fund, unload, freeze and terminate cards
cards:sensitiveRetrieve full card number and CVV (see card details)
transactions:readRead card and balance transactions
webhooks:writeManage webhook endpoints
Restrict each key to your server IPs (IP allowlist) and rotate keys every 90 days. Never expose keys in browsers or mobile apps.

Test & live mode

One base URL serves both modes; the key decides. Test mode behaves like live, except: KYC is approved automatically, cards cannot make real purchases, and your test balance starts with simulated funds. Objects created in test mode are never visible in live mode.

Requests & responses

Send JSON bodies with Content-Type: application/json. Amounts are decimal US dollars (e.g. 50.00). Timestamps are ISO 8601 UTC. Every response uses the same envelope:

200{
  "data": { "id": "card_3KpX9", "status": "active", "...": "..." },
  "meta": { "request_id": "req_01HF7Q…", "environment": "test" }
}

Errors

402{
  "error": { "type": "balance_error", "code": "insufficient_balance", "message": "Your balance is $12.40; this request needs $70.00." },
  "meta": { "request_id": "req_01HF7R…", "environment": "live" }
}
HTTPcodeMeaning
400invalid_requestMissing or invalid field (see error.param)
401invalid_api_keyKey missing, wrong or revoked
402insufficient_balanceYour Gpaynow balance can't cover the amount plus fees
402insufficient_card_balanceCard balance too low for the unload
403insufficient_scope · ip_not_allowed · live_not_enabledKey lacks the scope, IP not allowlisted, or live access not approved
404not_foundObject doesn't exist or isn't yours
409idempotency_conflictSame Idempotency-Key reused with a different body
409card_has_pending_transactionsCard can't be terminated until pending transactions settle (24–72 h)
422cardholder_kyc_not_approved · cardholder_email_exists · kyc_field_lockedCardholder state blocks the action
429rate_limitedToo many requests — retry after X-RateLimit-Reset
503issuing_unavailableOur issuing partner is temporarily unavailable — safe to retry with the same Idempotency-Key

Retry only on 429 and 5xx, with exponential backoff (max 4 attempts), always reusing the same Idempotency-Key.

Idempotency

Send an Idempotency-Key header (any unique string up to 255 characters, e.g. a UUID) on every POST. For 24 hours, repeating the request with the same key returns the original response with Idempotency-Replayed: true — so a network retry never issues two cards or loads money twice. Reusing a key with a different body returns 409 idempotency_conflict.

Pagination

List endpoints accept limit (1–100, default 20) and cursor. Responses include "has_more" and "next_cursor":

GET /cards?limit=2{ "data": [ { "id": "card_3KpX9" }, { "id": "card_8WmQ1" } ], "has_more": true, "next_cursor": "eyJpZCI6ImNhcmRfOFdtUTEifQ", "meta": { "...": "..." } }

Rate limits

Default: 60 requests per minute per key (higher limits on request). Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Exceeding the limit returns 429 rate_limited.

Account

GET/pingany key

Checks that your key works and shows the mode.

200{ "data": { "ok": true }, "meta": { "request_id": "req_…", "environment": "test" } }
GET/accountany key

Your API client profile: company, plan, live access status and the scopes of the key in use.

200{ "data": { "id": "cli_2Vh8", "company": "Acme Fintech Ltd", "plan": "standard", "live_enabled": true, "key_scopes": ["cards:read","cards:write"] } }
GET/balanceany key

Your available Gpaynow balance in USD. Top it up from the dashboard with any stablecoin (USDT, USDC and more). Each top-up costs a flat $1.00, whatever the amount.

200{ "data": { "available": 1240.50, "currency": "USD" } }
GET/ledgertransactions:read

Movements on your Gpaynow balance: deposits, card loads, unloads, refunds and fees.

QueryDescription
typedeposit · card_load · card_unload · card_refund · fee
card_idOnly entries for one card
from / toISO dates
200{ "data": [ { "id": "led_5Tq", "type": "card_load", "amount": -50.00, "fee": 0.00, "balance_after": 1240.50, "card_id": "card_3KpX9", "created_at": "2026-10-16T09:12:44Z" } ], "has_more": false }
GET/feesany key

The live fee schedule for your account. Only card_issue depends on your plan (Starter $5.00, Growth $3.00, Scale $0.50); every other fee is the same on all plans. Fees are charged to your Gpaynow balance at the moment of each action. Always read fees from this endpoint — they can change with notice.

200{ "data": {
  "plan": "growth",
  "card_issue": 3.00, "card_monthly": 0.00,
  "card_fund": 0.00, "transaction": 0.05,
  "decline": 0.00, "freeze": 0.00,
  "balance_topup": 1.00, "currency": "USD"
} }
Early access: the final fee schedule is confirmed before you go live. Plan prices are on the API pricing page.

Card products

A product defines the card network, BIN, issuing country and features (3-D Secure, Apple Pay / Google Pay, reloadable, single-use).

GET/productscards:read
200{ "data": [ {
  "id": "prd_visa_us_01", "network": "visa", "bin": "4xxxxx", "issuing_country": "US", "currency": "USD",
  "features": { "three_d_secure": true, "apple_pay": true, "google_pay": true, "reloadable": true, "single_use": false },
  "limits": { "max_load": 10000.00, "spending_limit": 1000.00, "spending_period": "monthly" },
  "card_validity_months": 36
} ] }
GET/restricted-merchantscards:read

Merchants and merchant categories that are blocked at network level (for example gambling). Purchases there are always declined.

Cardholders

A cardholder is the person who owns and uses cards. Identity verification (KYC) must be approved before cards can be issued in live mode.

POST/cardholderscardholders:write
FieldTypeDescription
first_namerequiredstringLegal first name
last_namerequiredstringLegal last name
emailrequiredstringUnique per cardholder; receives verification and security codes
date_of_birthrequiredYYYY-MM-DDMust be 18+
nationalityrequiredISO-3166 alpha-2e.g. NG
addressrequiredobjectline1, line2, city, state, postal_code, country
phoneE.164e.g. +2348012345678
external_idstringYour own user ID, for reconciliation
metadataobjectUp to 20 keys of your own data
201{ "data": { "id": "ch_9LqT2", "status": "active", "kyc_status": "pending", "external_id": "user_8841", "cards_count": 0, "created_at": "2026-10-16T09:10:02Z" } }
GET/cardholderscardholders:read

Filters: kyc_status (pending · approved · rejected), status, external_id, search (name or email).

GET/cardholders/{id}cardholders:read

Full record, including KYC status, rejection reason and which products the cardholder is eligible for.

200{ "data": { "id": "ch_9LqT2", "kyc_status": "approved", "eligible_products": ["prd_visa_us_01"], "cards_count": 1 } }
PATCH/cardholders/{id}cardholders:write

Partial update. After KYC approval, identity fields (name, date of birth, nationality) are locked and return 422 kyc_field_locked.

POST/cardholders/{id}/kyccardholders:write

Starts identity verification. When a hosted verification link is available, it is returned in verification_url — send it to the cardholder. The result arrives by webhook (cardholder.kyc_approved or cardholder.kyc_rejected).

200{ "data": { "id": "ch_9LqT2", "kyc_status": "pending", "verification_url": "https://verify.example/…" } }
Early access: the exact verification flow (hosted link or document upload) is confirmed during onboarding.
POST/cardholders/{id}/suspendcardholders:write
POST/cardholders/{id}/reactivatecardholders:write

Suspending freezes all the cardholder's cards; reactivating unfreezes the cards that the suspension froze. Optional body: {"reason": "…"}.

Cards

POST/cardscards:write

Issues a virtual card and loads it. The amount plus the issue fee are taken from your Gpaynow balance. Issuing is usually instant; the card starts as creating and becomes active — listen for card.issued.

FieldTypeDescription
cardholder_idrequiredstringA cardholder with approved KYC
product_idrequiredstringFrom GET /products
amountrequirednumberInitial load in USD (within the product's limits)
name_on_cardstringDefaults to the cardholder's name
metadataobjectYour own data
201{ "data": {
  "id": "card_3KpX9", "cardholder_id": "ch_9LqT2", "product_id": "prd_visa_us_01",
  "status": "creating", "network": "visa", "last4": "4821", "masked_number": "4xxx xxxx xxxx 4821",
  "expiry": "10/29", "name_on_card": "AMINA OKAFOR", "balance": 50.00, "currency": "USD",
  "billing_address": { "line1": "…", "city": "…", "country": "US" },
  "fee_charged": 3.00, "created_at": "2026-10-16T09:12:44Z"
} }
GET/cardscards:read

Filters: cardholder_id, status (creating · active · frozen · terminated).

GET/cards/{id}cards:read

Card details with live balance, amount on hold and pending authorisations. The card number is always masked here.

200{ "data": { "id": "card_3KpX9", "status": "active", "balance": 48.20, "on_hold": 1.80, "pending_authorizations": 1, "last4": "4821" } }
GET/cards/{id}/sensitivecards:sensitive

Returns the full card number, CVV and expiry so your cardholder can pay online. Every call is logged.

200{ "data": { "id": "card_3KpX9", "number": "4…", "cvv": "…", "expiry_month": 10, "expiry_year": 2029 } }
PCI DSS: handling full card numbers puts your systems in PCI DSS scope. Never store or log these values. Early access: for some programs, card details are shown through a secure hosted card-reveal widget instead of this endpoint.
POST/cards/{id}/fundcards:write

Moves money from your Gpaynow balance onto the card, free of charge. Body: {"amount": 25.00, "reference": "your-unique-ref"}. The reference is single-use.

200{ "data": { "card_id": "card_3KpX9", "amount": 25.00, "fee_charged": 0.00, "card_balance": 73.20, "reference": "topup-7781" } }
POST/cards/{id}/unloadcards:write

Moves money from the card back to your Gpaynow balance. Body: {"amount": 20.00, "reference": "…"}. Returns 402 insufficient_card_balance if the card can't cover it.

POST/cards/{id}/limitscards:write

Sets spending limits in USD. At least one is required: daily, monthly, per_transaction.

body{ "daily": 200.00, "per_transaction": 50.00 }
POST/cards/{id}/freezecards:write
POST/cards/{id}/unfreezecards:write

Freezing blocks new purchases instantly; the balance stays on the card. Unfreeze to resume.

POST/cards/{id}/terminatecards:write

Permanently closes the card and returns its remaining balance to your Gpaynow balance. This can't be undone. Body must include {"confirm": "TERMINATE_CARD"}. Returns 409 card_has_pending_transactions until pending purchases settle (typically 24–72 hours).

GET/cards/{id}/transactionstransactions:read

Purchases, declines, refunds, reversals and fees on the card.

200{ "data": [ {
  "id": "txn_7Rk2", "type": "purchase", "status": "settled", "amount": 1.80, "currency": "USD",
  "merchant": { "name": "OPENAI *CHATGPT", "category": "5734", "country": "US" },
  "decline_reason": null, "created_at": "2026-10-16T10:01:12Z"
} ], "has_more": false }

status: pending · settled · declined · reversed.

POST/cards/{id}/statementtransactions:read

Generates a PDF statement for a date range (from, to). Returns a download link valid for 24 hours.

Webhooks

Webhooks push events to your server as they happen — card issued, transaction authorised, KYC approved and more. Your endpoint must use HTTPS and answer with any 2xx within 10 seconds. Failed deliveries are retried after 1 min, 10 min, 1 h, 6 h and 24 h; you can replay any delivery from the dashboard.

event{
  "id": "evt_01HF8A…", "type": "transaction.authorized", "created_at": "2026-10-16T10:01:12Z", "environment": "live",
  "data": { "card_id": "card_3KpX9", "transaction_id": "txn_7Rk2", "amount": 1.80, "merchant": { "name": "OPENAI *CHATGPT" } }
}

Events can arrive more than once or out of order — de-duplicate on id.

Manage endpoints

POST/webhookswebhooks:write

Body: {"url": "https://example.com/gpaynow/webhook", "events": ["card.*", "transaction.*"]}. The response contains the signing secret once — store it safely.

GET/webhookswebhooks:write
PATCH/webhooks/{id}webhooks:write
DELETE/webhooks/{id}webhooks:write
POST/webhooks/{id}/testwebhooks:write

Event types

EventWhen
card.issuedCard is active and ready to use
card.issue_failedIssuing failed — the load and fee are refunded to your balance
card.funded · card.fund_failedTop-up completed / failed (failed top-ups are refunded)
card.unloadedMoney moved back to your balance
card.frozen · card.unfrozenCard blocked / unblocked
card.terminatedCard closed; refund amount included
card.3ds_otpOne-time code for an online checkout — deliver it to the cardholder
card.tokenization_otpOne-time code to add the card to Apple Pay / Google Pay
transaction.authorizedPurchase approved (pending settlement)
transaction.clearedPurchase settled
transaction.declinedPurchase declined, with decline_reason
transaction.reversedAuthorisation reversed or refunded
transaction.feeA fee was charged on the card
cardholder.kyc_approved · cardholder.kyc_rejectedVerification result
cardholder.suspended · cardholder.reactivatedCardholder status changed
balance.lowYour Gpaynow balance fell below your alert threshold

Verify signatures

Every delivery carries Gpaynow-Signature: t=<unix_seconds>,v1=<hex>. Compute HMAC-SHA256(secret, t + "." + raw_body), compare in constant time, and reject events older than 5 minutes. Always use the raw request body.

Node.jsimport crypto from "node:crypto";
export function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
PHPfunction gpaynow_verify(string $raw, string $header, string $secret): bool {
    parse_str(str_replace(',', '&', $header), $p);
    if (abs(time() - (int) ($p['t'] ?? 0)) > 300) return false;
    $expected = hash_hmac('sha256', $p['t'] . '.' . $raw, $secret);
    return hash_equals($expected, $p['v1'] ?? '');
}
// $raw = file_get_contents('php://input');
Pythonimport hmac, hashlib, time
def verify(raw: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

3-D Secure & wallet codes

When a merchant asks for 3-D Secure, the one-time code is sent to you in a card.3ds_otp event, not by SMS. Show it to the cardholder in your app or forward it by email/SMS within its validity (about 5 minutes). Adding a card to Apple Pay or Google Pay works the same way with card.tokenization_otp.

event{ "id": "evt_…", "type": "card.3ds_otp", "data": { "card_id": "card_3KpX9", "code": "482913", "expires_in_seconds": 300, "merchant": "NETFLIX.COM" } }

Compliance & acceptable use

  • Business verification (KYB) is required before live keys are issued.
  • Cardholder KYC is required for every cardholder in live mode. You must collect accurate data and may not create cardholders for people who haven't consented.
  • Prohibited: gambling, sanctioned countries or persons, evading merchant or regional restrictions, ATM withdrawals, and any use banned by card-network rules.
  • Liability: you are responsible for fraud, disputes and chargebacks on cards you issue, as set out in the Gpaynow API Terms.
  • At launch, cards will be issued by a licensed card-issuing partner under licence from Visa or Mastercard (no issuer is connected yet). Gpaynow and its API clients are not card issuers and must not present themselves as such.

Changelog

VersionDateNotes
2.0.0-preview2026-10-16Early-access preview of the v2 reference. Endpoints open to early-access members first.
Early access

Be first to issue cards with the Gpaynow API

Tell us about your project. Our team reviews every request and contacts you with sandbox access and onboarding details before launch.

  • No payment now; you choose a plan at launch
  • Sandbox keys before the public launch
  • Help from our team with integration and business verification
  • Reply on WhatsApp, Telegram or email within 1–2 business days
Prefer to talk now? Message our team directly. We usually reply within minutes.
How should we contact you?
Plain text only: no files, code or HTML. By sending this form you agree that Gpaynow may contact you about the Card API. Live access requires business verification (KYB). See our privacy policy.

We may use cookies or any other tracking technologies when you visit our website, including any other media form, mobile website, or mobile application related or connected to help customize the Site and improve your experience.

Learn More Accept All