Desenvolvedores/Primeiros passos
Autenticação
Toda chamada à API leva o seu Merchant ID e a sua chave de API.
Ao fazer um pedido (POST/orders), você também envia uma assinatura. Assim ninguém consegue alterar nem repetir o pedido.
Veja também: Catálogo e pedidos · Segurança · README
#Credenciais
| Credencial | Exemplo | É secreta? |
|---|---|---|
| Merchant ID | M00000001 | Não. Só identifica quem você é. |
| Chave de API | cvb2b_... | Sim. É a sua senha e também a chave de assinatura. |
- A CardV envia o Merchant ID quando a sua conta é aprovada.
- Um Owner cria a chave de API no Portal (veja Chaves de API).
- Live e Sandbox têm chaves diferentes. 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 dois cabeçalhos em toda chamada:
X-Merchant-Id: M00000001
X-Api-Key: cvb2b_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxEm POST/orders, envie também estes três:
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfedX-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 um pedido. Precisa estar em hexadecimal minúsculo.
Chamadas GET não precisam de assinatura.
#Como assinar um pedido
Transforme o pedido em texto JSON uma única vez. Você vai assinar e enviar exatamente esses bytes.
Calcule o hash do corpo:
BODY_HASH= SHA-256 do corpo, em hexadecimal minúsculo.Junte estas cinco linhas com uma quebra de linha (
\n), sem quebra de linha no final:POST /api/v1/orders <X-Timestamp> <X-Nonce> <BODY_HASH>Assine:
X-Signature= HMAC-SHA256 desse texto, usando a chave de API completa como chave. Escreva o resultado em hexadecimal minúsculo.
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 depois que você assina.
Vetor de teste. Use estes valores para conferir o seu código. A chave é falsa.
Chave de API cvb2b_TEST_ONLY_not_a_real_key_0123456789abcdef
Método POST
Caminho /api/v1/orders
X-Timestamp 1790000000
X-Nonce 0123456789abcdef0123456789abcdefCorpo (108 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.2500"}]}Resultado esperado:
BODY_HASH b0545ae25d54b219f27d8bd90e4dcbf26cf0d491f53982da67b8eef0a1a59960
X-Signature fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfed#Exemplos de código
Todos os exemplos chegam ao resultado do vetor de teste. Carregue a chave do seu cofre de segredos, nunca a 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.2500"}]}'
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_API_KEY" -hex | sed 's/^.*= //')
curl -sS -X POST "$BASE_URL$REQ_PATH" \
-H "Content-Type: application/json" \
-H "X-Merchant-Id: $CARDV_MERCHANT_ID" \
-H "X-Api-Key: $CARDV_API_KEY" \
-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.
Python (requests)
import hashlib, hmac, json, os, secrets, time
import requests
BASE_URL = "https://sandbox.cardv.net"
MERCHANT_ID = os.environ["CARDV_MERCHANT_ID"]
API_KEY = os.environ["CARDV_API_KEY"]
AUTH = {"X-Merchant-Id": MERCHANT_ID, "X-Api-Key": API_KEY}
def sign(path: str, body: bytes, ts: str, nonce: str) -> str:
body_hash = hashlib.sha256(body).hexdigest()
text = "\n".join(["POST", path, ts, nonce, body_hash])
return hmac.new(API_KEY.encode(), text.encode(), hashlib.sha256).hexdigest()
def cardv_get(path: str, params=None) -> requests.Response:
return requests.get(BASE_URL + path, params=params, headers=AUTH, timeout=30)
def cardv_post(path: str, payload: dict) -> requests.Response:
body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode()
ts, nonce = str(int(time.time())), secrets.token_hex(16)
headers = {
**AUTH,
"Content-Type": "application/json",
"X-Timestamp": ts,
"X-Nonce": nonce,
"X-Signature": sign(path, body, ts, nonce),
}
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.2500"}],
})
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_MERCHANT_ID, CARDV_API_KEY } = process.env;
const AUTH = { "X-Merchant-Id": CARDV_MERCHANT_ID, "X-Api-Key": CARDV_API_KEY };
function sign(path, body, timestamp, nonce) {
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
const text = ["POST", path, timestamp, nonce, bodyHash].join("\n");
return crypto.createHmac("sha256", CARDV_API_KEY).update(text, "utf8").digest("hex");
}
async function cardvPost(path, payload) {
const body = Buffer.from(JSON.stringify(payload), "utf8"); // serialize once
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const headers = {
...AUTH,
"Content-Type": "application/json",
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Signature": sign(path, body, timestamp, nonce),
};
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.2500" }],
}));PHP (só a assinatura)
<?php
function cardv_sign(
string $apiKey, string $path, string $body, string $ts, string $nonce
): string {
$text = implode("\n", ['POST', $path, $ts, $nonce, hash('sha256', $body)]);
return hash_hmac('sha256', $text, $apiKey); // lowercase hex
}
// Send exactly $body.
$body = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));
$signature = cardv_sign($apiKey, '/api/v1/orders', $body, $ts, $nonce);#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 |
|---|---|
| Merchant ID ou chave de API ausente ou incorreto | Confira os dois cabeçalhos e o ambiente. |
| 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ó sete endpoints funcionam com chave de API. |
| Assinatura ausente ou incorreta | Corrija o código de assinatura. Teste com o vetor 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 pedido. |
| Requisições demais (429) | Espere os segundos indicados em Retry-After e tente de novo. |
Importante:
- Ao reenviar um pedido, 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 sete endpoints listados em Visão geral da API. Isso inclui fazer pedidos e ler códigos, então proteja a chave 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 ainda não mostra o valor da chave.
- Exiba a chave (Reveal). A CardV envia por e-mail um código de 6 dígitos, válido por 10 minutos (5 tentativas). Digite o código para ver a chave completa. Guarde-a na hora. Toda exibição fica registrada.
- Desative a chave quando não precisar mais dela. A desativação é imediata e definitiva.
Para trocar de chave sem parar o serviço:
- Crie e exiba uma chave nova.
- 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.
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.