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
| Credencial | Exemplo | É secreta? |
|---|---|---|
| ID da chave | ck_live_... | Não. Só diz qual chave você está usando. |
| Segredo de assinatura | cs_live_... | Sim. Você assina com ele. Ele nunca sai do seu servidor. |
| Merchant ID | M00000001 | Nã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_ecs_live_. Chaves do Sandbox começam comck_test_ecs_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/accountmostra"api_access_enabled": true.
#Cabeçalhos
Envie estes quatro cabeçalhos em toda chamada, inclusive nas chamadas GET:
X-Key-Id: ck_live_xxxxxxxxxxxxxxxxxxxxxxxx
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: e6e45d09ed271d8d721701b560d56a6f5104fd8a52aaad5380166f432bba6e89X-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-Signatureestá 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
Transforme o corpo da chamada em bytes uma única vez. Você vai assinar e enviar exatamente esses bytes. Uma chamada
GETtem o corpo vazio.Calcule o hash do corpo:
BODY_HASH= SHA-256 do corpo, em hexadecimal minúsculo. Para um corpo vazio, o resultado ée3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.Junte estas cinco linhas com uma quebra de linha (
\n), sem quebra de linha no final:<METHOD> <PATH with query string> <X-Timestamp> <X-Nonce> <BODY_HASH>METHODfica em maiúsculas, por exemploGETouPOST. O caminho inclui a query string exatamente como você a envia, por exemplo/api/v1/skus?limit=50.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.
Signing secret cs_test_TEST_ONLY_not_a_real_secret_0123456789abcdef
X-Timestamp 1790000000
X-Nonce 0123456789abcdef0123456789abcdefUma chamada GET:
Method GET
Path /api/v1/skus?limit=50
BODY_HASH e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Signature 58f0362c355a6624fff9f0b84d47591c570263908832294375b7bf27731628e5Uma chamada POST para /api/v1/orders com este corpo (106 bytes, uma linha só, sem quebra de linha no final):
{"external_order_id":"TEST-0001","items":[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.25"}]}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)
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)
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)
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
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:
{"detail": "Invalid HMAC signature."}| Problema | O que fazer |
|---|---|
| ID da chave desconhecido ou desativado | Confira o X-Key-Id e o ambiente (Live ou Sandbox). |
| Acesso à API não liberado | Aguarde a CardV aprovar a sua empresa. |
| O IP do seu servidor não está liberado | Adicione o IP à lista de IPs permitidos no Portal. |
| Endpoint disponível só no Portal | Use o Portal. Só dezesseis endpoints funcionam com chave de API. |
| Assinatura ausente ou incorreta | Corrija o código de assinatura. Teste com os vetores de teste. |
| Timestamp muito antigo ou adiantado | Sincronize o relógio do servidor (NTP). |
| Nonce já usado | Gere 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) ou2001: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.
- 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.
- 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.
- 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:
- Crie uma chave nova e guarde o segredo dela.
- Instale a chave nova em todos os seus servidores.
- Confira no Portal que a chave antiga não está mais sendo usada.
- 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.