Desenvolvedores/Primeiros passos

Autenticação

Toda chamada à API leva o ID da sua chave e uma assinatura. A assinatura prova que a chamada vem de você, e ninguém consegue alterar nem repetir a chamada. O seu segredo de assinatura nunca é enviado.

Veja também: Catálogo e pedidos · Segurança · README

#Credenciais

CredencialExemploÉ secreta?
ID da chaveck_live_...Não. Só diz qual chave você está usando.
Segredo de assinaturacs_live_...Sim. Você assina com ele. Ele nunca sai do seu servidor.
Merchant IDM00000001Não. É opcional nas chamadas à API.
  • Um Owner cria as chaves no Portal (veja Chaves de API).
  • O Portal mostra o segredo de assinatura uma única vez, quando você cria a chave. Ninguém consegue mostrá-lo de novo, nem a CardV.
  • Chaves do Live começam com ck_live_ e cs_live_. Chaves do Sandbox começam com ck_test_ e cs_test_. A chave de um ambiente nunca funciona no outro.
  • A API só funciona depois que a CardV aprova a sua empresa. A partir daí, GET/account mostra "api_access_enabled": true.

#Cabeçalhos

Envie estes quatro cabeçalhos em toda chamada, inclusive nas chamadas GET:

HTTP
X-Key-Id: ck_live_xxxxxxxxxxxxxxxxxxxxxxxx
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: e6e45d09ed271d8d721701b560d56a6f5104fd8a52aaad5380166f432bba6e89
  • X-Key-Id é o ID público da sua chave.
  • X-Timestamp é a hora atual em Unix, em segundos. Ela não pode diferir mais de 5 minutos (300 segundos) do relógio da CardV.
  • X-Nonce é um valor aleatório que você nunca repete. Use 32 caracteres hexadecimais aleatórios.
  • X-Signature está explicada em Como assinar uma chamada. Precisa estar em hexadecimal minúsculo.

Você também pode enviar X-Merchant-Id. Se enviar, ele precisa corresponder à chave.

#Como assinar uma chamada

  1. Transforme o corpo da chamada em bytes uma única vez. Você vai assinar e enviar exatamente esses bytes. Uma chamada GET tem o corpo vazio.

  2. Calcule o hash do corpo: BODY_HASH = SHA-256 do corpo, em hexadecimal minúsculo. Para um corpo vazio, o resultado é e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

  3. Junte estas cinco linhas com uma quebra de linha (\n), sem quebra de linha no final:

    Text
    <METHOD>
    <PATH with query string>
    <X-Timestamp>
    <X-Nonce>
    <BODY_HASH>

    METHOD fica em maiúsculas, por exemplo GET ou POST. O caminho inclui a query string exatamente como você a envia, por exemplo /api/v1/skus?limit=50.

  4. Assine: X-Signature = HMAC-SHA256 desse texto, usando o seu segredo de assinatura como chave. Escreva o resultado em hexadecimal minúsculo.

As chamadas de recarga de celular são assinadas do mesmo jeito, com o caminho de cada uma, por exemplo /api/v1/recharge/orders.

O erro mais comum é assinar uma versão do JSON e enviar outra. Por exemplo, {"a":1} e {"a": 1} são bytes diferentes. Confira se a sua biblioteca HTTP não altera o corpo nem reordena a query string depois que você assina.

Vetores de teste. Use estes valores para conferir o seu código. O segredo é falso.

Text
Signing secret  cs_test_TEST_ONLY_not_a_real_secret_0123456789abcdef
X-Timestamp     1790000000
X-Nonce         0123456789abcdef0123456789abcdef

Uma chamada GET:

Text
Method       GET
Path         /api/v1/skus?limit=50
BODY_HASH    e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Signature  58f0362c355a6624fff9f0b84d47591c570263908832294375b7bf27731628e5

Uma chamada POST para /api/v1/orders com este corpo (106 bytes, uma linha só, sem quebra de linha no final):

JSON
{"external_order_id":"TEST-0001","items":[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.25"}]}
Text
BODY_HASH    5814ffcdadfa9eb69348150f9fd6895c00b4c37cf6cd0202b83174354645023e
X-Signature  e6e45d09ed271d8d721701b560d56a6f5104fd8a52aaad5380166f432bba6e89

#Exemplos de código

Todos os exemplos chegam aos resultados dos vetores de teste. Carregue o segredo do seu cofre de segredos, nunca o deixe no código.

cURL (bash + OpenSSL)

Shell
BASE_URL="https://sandbox.cardv.net"
REQ_PATH="/api/v1/orders"      # do not call this variable PATH
BODY='{"external_order_id":"SHOP-10001","items":'
BODY+='[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.25"}]}'
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.*= //')
SIG=$(printf 'POST\n%s\n%s\n%s\n%s' "$REQ_PATH" "$TS" "$NONCE" "$BODY_HASH" \
  | openssl dgst -sha256 -hmac "$CARDV_SIGNING_SECRET" -hex | sed 's/^.*= //')

curl -sS -X POST "$BASE_URL$REQ_PATH" \
  -H "Content-Type: application/json" \
  -H "X-Key-Id: $CARDV_KEY_ID" \
  -H "X-Timestamp: $TS" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIG" \
  --data-raw "$BODY"

Use --data-raw, e não -d @file. O -d remove as quebras de linha, e os bytes enviados deixam de bater com a assinatura. Para uma chamada GET, assine GET, o caminho com a query string e o hash do corpo vazio, e não envie corpo.

Python (requests)

Python
import hashlib, hmac, json, os, secrets, time
import requests

BASE_URL = "https://sandbox.cardv.net"
KEY_ID = os.environ["CARDV_KEY_ID"]
SIGNING_SECRET = os.environ["CARDV_SIGNING_SECRET"]


def signed_headers(method: str, path: str, body: bytes) -> dict:
    ts, nonce = str(int(time.time())), secrets.token_hex(16)
    body_hash = hashlib.sha256(body).hexdigest()
    text = "\n".join([method, path, ts, nonce, body_hash])
    signature = hmac.new(SIGNING_SECRET.encode(), text.encode(), hashlib.sha256).hexdigest()
    return {"X-Key-Id": KEY_ID, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": signature}


def cardv_get(path: str) -> requests.Response:
    # Put the query string in `path` so the signed path and the sent path are identical.
    return requests.get(BASE_URL + path, headers=signed_headers("GET", path, b""), timeout=30)


def cardv_post(path: str, payload: dict) -> requests.Response:
    body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode()
    headers = {**signed_headers("POST", path, body), "Content-Type": "application/json"}
    return requests.post(BASE_URL + path, data=body, headers=headers, timeout=30)


resp = cardv_post("/api/v1/orders", {
    "external_order_id": "SHOP-10001",
    "items": [{"sku_id": "S000001", "quantity": 1, "expected_unit_price": "9.25"}],
})
print(resp.status_code, resp.json())

order_id = resp.json()["order"]["order_id"]
print(cardv_get(f"/api/v1/orders/{order_id}").json()["status"])

Node.js 18+ (fetch nativo)

Node.js
import crypto from "node:crypto";

const BASE_URL = "https://sandbox.cardv.net";
const { CARDV_KEY_ID, CARDV_SIGNING_SECRET } = process.env;

function signedHeaders(method, path, body) {
  const timestamp = String(Math.floor(Date.now() / 1000));
  const nonce = crypto.randomBytes(16).toString("hex");
  const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
  const text = [method, path, timestamp, nonce, bodyHash].join("\n");
  const signature = crypto.createHmac("sha256", CARDV_SIGNING_SECRET).update(text, "utf8").digest("hex");
  return { "X-Key-Id": CARDV_KEY_ID, "X-Timestamp": timestamp, "X-Nonce": nonce, "X-Signature": signature };
}

async function cardvGet(path) {
  const res = await fetch(BASE_URL + path, { headers: signedHeaders("GET", path, Buffer.alloc(0)) });
  return { status: res.status, body: await res.json() };
}

async function cardvPost(path, payload) {
  const body = Buffer.from(JSON.stringify(payload), "utf8"); // serialize once
  const headers = { ...signedHeaders("POST", path, body), "Content-Type": "application/json" };
  const res = await fetch(BASE_URL + path, { method: "POST", headers, body });
  return { status: res.status, body: await res.json() };
}

console.log(await cardvPost("/api/v1/orders", {
  external_order_id: "SHOP-10001",
  items: [{ sku_id: "S000001", quantity: 1, expected_unit_price: "9.25" }],
}));
console.log(await cardvGet("/api/v1/balance"));

PHP (só a assinatura)

PHP
<?php
function cardv_sign(
    string $secret, string $method, string $path, string $body, string $ts, string $nonce
): string {
    $text = implode("\n", [$method, $path, $ts, $nonce, hash('sha256', $body)]);
    return hash_hmac('sha256', $text, $secret); // lowercase hex
}

// Send exactly $body. For a GET call use $body = ''.
$body = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));
$signature = cardv_sign($signingSecret, 'POST', '/api/v1/orders', $body, $ts, $nonce);
// Headers: X-Key-Id, X-Timestamp, X-Nonce, X-Signature

#Erros

Todos estes erros retornam HTTP 403 (e não 401), exceto o limite de requisições, que retorna 429. O corpo tem este formato:

JSON
{"detail": "Invalid HMAC signature."}
ProblemaO que fazer
ID da chave desconhecido ou desativadoConfira o X-Key-Id e o ambiente (Live ou Sandbox).
Acesso à API não liberadoAguarde a CardV aprovar a sua empresa.
O IP do seu servidor não está liberadoAdicione o IP à lista de IPs permitidos no Portal.
Endpoint disponível só no PortalUse o Portal. Só dezesseis endpoints funcionam com chave de API.
Assinatura ausente ou incorretaCorrija o código de assinatura. Teste com os vetores de teste.
Timestamp muito antigo ou adiantadoSincronize o relógio do servidor (NTP).
Nonce já usadoGere um nonce aleatório novo a cada chamada.
Requisições demais (429)Espere os segundos indicados em Retry-After e tente de novo.

Importante:

  • Ao reenviar uma chamada, gere timestamp, nonce e assinatura novos. Mantenha o corpo igual. Veja como reenviar com segurança.
  • Um nonce conta como usado mesmo quando a assinatura está errada.
  • A CardV registra as tentativas com falha e avisa a equipe dela se houver muitas.

#Lista de IPs permitidos

A lista de IPs permitidos (IP allowlist) garante que só os seus servidores usem a sua chave. Um Owner gerencia a lista no Portal.

  • É opcional. Sem nenhuma regra, chamadas de qualquer IP são aceitas.
  • Com pelo menos uma regra, chamadas de outros IPs recebem HTTP 403.
  • As regras têm o formato 203.0.113.10/32 (IPv4) ou 2001:db8::/48 (IPv6).
  • Vale só para chamadas com chave de API, não para o login no Portal.
  • Antes de ativar, adicione todos os IPs dos seus servidores, inclusive gateways NAT e regiões de backup.

#Chaves de API

Uma chave de API dá acesso exatamente aos dezesseis endpoints listados em Visão geral da API. Isso inclui fazer pedidos e ler códigos, então proteja o segredo de assinatura como a senha de um Owner.

Só um Owner pode gerenciar chaves, no Portal, em Integrations → API keys. Faça isso separadamente no Live e no Sandbox.

  1. Crie uma chave e dê um nome a ela. O Portal mostra o ID da chave e o segredo de assinatura. Copie o segredo para o seu cofre de segredos agora. Ele só aparece esta vez.
  2. Gere um novo segredo se você perder o seu. O ID da chave continua o mesmo. O segredo antigo para de funcionar na hora, então instale o novo em todos os seus servidores imediatamente.
  3. Desative a chave quando não precisar mais dela. A desativação é imediata e definitiva. Depois, você pode remover a chave desativada da lista. A CardV mantém o histórico dela.

Para trocar de chave sem parar o serviço:

  1. Crie uma chave nova e guarde o segredo dela.
  2. Instale a chave nova em todos os seus servidores.
  3. Confira no Portal que a chave antiga não está mais sendo usada.
  4. Desative a chave antiga.

Troque as chaves pelo menos uma vez por ano e sempre que sair alguém que tinha acesso a elas. Se houver chance de uma chave ter vazado, desative-a primeiro e investigue depois.

#Chaves legadas

Chaves criadas antes de outubro de 2026 começam com cvb2b_ e usam os cabeçalhos antigos X-Merchant-Id e X-Api-Key, com assinatura só nas chamadas POST. A chave antiga vai junto com cada requisição, então é mais fácil ela vazar.

O Live e o Sandbox não aceitam mais essas chaves. Mude para uma chave nova agora: crie uma no Portal, troque o seu código para os cabeçalhos acima e depois desative a chave antiga.

Dúvidas sobre a integração? Envie um e-mail para [email protected] com seu Merchant ID e o ID do pedido ou da requisição.