Developer documentation / API v1

Merchant API guide

Integrate prepaid digital goods through CardV's versioned REST API. The selected environment's OpenAPI reference defines the complete field schemas.

https://b2b.cardv.net Live · https://sandbox.cardv.net Sandbox · /api/v1

Integrate in sandbox first. Keys, wallets, SKUs, orders and supplier access are separate. A Live order may debit real funds and trigger supplier procurement.

Live API reference ↗ · Sandbox API reference ↗ · Live OpenAPI schema ↗ · Merchant Portal ↗

Get started

  1. Apply, verify email, await approval, then sign in to the merchant Portal. The immutable Merchant ID has format M########; it is not your company name or login email.
  2. Select Live or Sandbox in the Portal header, then open Integrations → API keys. Creation returns metadata; use Reveal and the emailed verification code to obtain and save the raw key.
  3. Call GET /api/v1/account and GET /api/v1/balance on the selected host.
  4. Browse SKUs, request a fresh quote and submit an order with a unique merchant reference.
  5. Poll order detail or verify webhooks until final fulfilment.

Authentication & HMAC signing

Every server-to-server request needs X-Merchant-Id and X-Api-Key. API-key authenticated POST, PUT, PATCH and DELETE requests also need X-Timestamp, X-Nonce and X-Signature. Timestamp is Unix seconds within five minutes; nonce is single-use in that window.

canonical = "\n".join([
  METHOD.upper(),
  PATH_WITH_QUERY,
  TIMESTAMP,
  NONCE,
  sha256(EXACT_RAW_BODY).hexdigest()
])
signature = hmac_sha256(API_KEY, canonical).hexdigest().lower()

Hash the exact raw body bytes you send, include the query string but not the host in the canonical path, and use the API key as the HMAC-SHA256 secret. Use lowercase hexadecimal. A retry needs a fresh timestamp and nonce. Swagger does not generate write-request HMAC headers.

A configured IP allowlist also applies. Never log API keys or full delivery payloads.

Catalog & pricing

EndpointPurpose / 用途
GET /api/v1/accountMerchant account / 商户账号
GET /api/v1/balanceWallet balance / 钱包余额
GET /api/v1/productsProduct catalog / 商品目录
GET /api/v1/skusOrderable SKUs / 可下单规格
GET /api/v1/skus/{sku_id}/quoteCurrent quote / 当前报价
POST /api/v1/ordersCreate order / 创建订单
GET /api/v1/orders/{order_id}Order detail / 订单详情
GET /api/v1/orders/by-external-id/{external_order_id}Lookup by merchant reference / 按商户订单号查询

Only order an SKU with availability=available. Quote a fixed denomination with quantity; a range denomination also needs amount in the SKU's face_currency between min_face_value and max_face_value. Send the returned merchant_price as expected_unit_price; do not derive it from face value or catalog previews.

GET /api/v1/skus/S000001/quote?quantity=1
GET /api/v1/skus/S000002/quote?quantity=1&amount=25.00

Orders, idempotency & delivery

POST /api/v1/orders accepts external_order_id and items. Each item needs sku_id and quantity; send expected_unit_price from a fresh quote. Include amount for a range SKU or inputs matching required_input_schema for direct charge.

{
  "external_order_id": "YOUR-ORDER-10001",
  "items": [{
    "sku_id": "S000001",
    "quantity": 1,
    "expected_unit_price": "9.2500"
  }]
}

The example SKU and price are illustrative. Obtain actual values from the selected environment. external_order_id is unique per merchant, 1–120 characters. A new order returns 201; an equivalent replay returns 200 with idempotent_replay=true. Changed content under the same reference is rejected.

Store public order.order_id (O-...), not the legacy numeric id. HTTP acceptance is not delivery completion. Query by order ID or external_order_id. States include accepted, processing, succeeded, partially_succeeded, failed, cancelled and refunded. Fulfilment failure does not itself prove a wallet refund: reconcile transactions. API-key order detail may contain card secrets; disable caching and sensitive response logs.

Mobile recharge is a separate flow

Do not use /api/v1/orders for mobile recharge. Discover GET /api/v1/recharge/countries and GET /api/v1/recharge/operators?country=..., then POST /api/v1/recharge/quote with country, operator_key, amount and optional subtype. Use its quote_token, recipient account, amount and a unique external_order_id with POST /api/v1/recharge/orders. Query GET /api/v1/recharge/orders/{order_id}.

Webhooks

A Portal owner registers an HTTPS endpoint in the selected environment. Creation/reset returns signing_secret once; list/detail do not reveal it. CardV sends X-CardV-Event, X-CardV-Delivery, X-CardV-Timestamp and X-CardV-Signature: t=TIMESTAMP,v1=HEX.

expected = hmac_sha256(
  WEBHOOK_SECRET,
  X_CARDV_TIMESTAMP + "." + EXACT_RAW_BODY
).hexdigest().lower()

Compare in constant time, reject stale timestamps and persist before returning 2xx. Deduplicate by delivery ID and by business order/state for replays. Webhooks are notifications, not full card-secret payloads; retrieve delivery via authenticated order detail.

Errors & safe retries

HTTPAction
400Validation, price or input error; correct the request.
403 + CardV JSONCheck credentials, signature, access and source IP.
403 + Cloudflare 1010Edge blocked the request before CardV; rotating keys will not help.
404Resource unavailable to this merchant.
429Respect Retry-After.
5xx / timeoutQuery by external_order_id first; if unresolved, retry the same content/reference with fresh timestamp and nonce.

Environments & current limitation

Sign in once at the Live merchant Portal and switch workspace in its header; direct sandbox password login is disabled. Each environment has its own OpenAPI, keys, wallet, SKUs and orders. Provisioned sandbox merchants receive 1,000 USD noncash test credit and a limited catalog; real funding is unavailable.

Coordinate any low-value Live test with your CardV contact. Never send API keys, webhook secrets or card deliveries by email.