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

CredencialExemploÉ secreta?
Merchant IDM00000001Não. Só identifica quem você é.
Chave de APIcvb2b_...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/account mostra "api_access_enabled": true.

#Cabeçalhos

Envie estes dois cabeçalhos em toda chamada:

HTTP
X-Merchant-Id: M00000001
X-Api-Key: cvb2b_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Em POST/orders, envie também estes três:

HTTP
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfed
  • 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 um pedido. Precisa estar em hexadecimal minúsculo.

Chamadas GET não precisam de assinatura.

#Como assinar um pedido

  1. Transforme o pedido em texto JSON uma única vez. Você vai assinar e enviar exatamente esses bytes.

  2. Calcule o hash do corpo: BODY_HASH = SHA-256 do corpo, em hexadecimal minúsculo.

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

    Text
    POST
    /api/v1/orders
    <X-Timestamp>
    <X-Nonce>
    <BODY_HASH>
  4. 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.

Text
Chave de API  cvb2b_TEST_ONLY_not_a_real_key_0123456789abcdef
Método        POST
Caminho       /api/v1/orders
X-Timestamp   1790000000
X-Nonce       0123456789abcdef0123456789abcdef

Corpo (108 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.2500"}]}

Resultado esperado:

Text
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)

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.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)

Python
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)

Node.js
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
<?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:

JSON
{"detail": "Invalid HMAC signature."}
ProblemaO que fazer
Merchant ID ou chave de API ausente ou incorretoConfira os dois cabeçalhos e o ambiente.
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ó sete endpoints funcionam com chave de API.
Assinatura ausente ou incorretaCorrija o código de assinatura. Teste com o vetor de teste.
Timestamp muito antigo ou adiantadoSincronize o relógio do servidor (NTP).
Nonce já usadoGere 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) 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 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.

  1. Crie uma chave e dê um nome a ela. O Portal ainda não mostra o valor da chave.
  2. 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.
  3. Desative a chave quando não precisar mais dela. A desativação é imediata e definitiva.

Para trocar de chave sem parar o serviço:

  1. Crie e exiba uma chave nova.
  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.

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.