Développeurs/Démarrer

Authentification

Chaque appel à l'API contient votre Merchant ID et votre clé API. Le passage de commande (POST/orders) contient en plus une signature : personne ne peut ainsi modifier ni rejouer votre commande.

Voir aussi : Catalogue et commandes · Sécurité · README

#Identifiants de connexion

IdentifiantExempleSecret ?
Merchant IDM00000001Non. Il indique qui vous êtes.
Clé APIcvb2b_...Oui. C'est à la fois votre mot de passe et votre clé de signature.
  • CardV vous attribue le Merchant ID (votre identifiant marchand) une fois votre compte validé.
  • Un Owner crée la clé API dans le Portail (voir Clés API).
  • Live et Sandbox ont des clés différentes. Une clé de l'un ne fonctionne jamais dans l'autre.
  • L'API ne fonctionne qu'après la validation de votre entreprise par CardV. GET/account affiche alors "api_access_enabled": true.

#En-têtes

Envoyez ces deux en-têtes à chaque appel :

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

Sur POST/orders, envoyez aussi ces trois-là :

HTTP
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfed
  • X-Timestamp est l'heure Unix actuelle, en secondes. Elle ne doit pas s'écarter de plus de 5 minutes (300 secondes) de l'horloge de CardV.
  • X-Nonce est une valeur aléatoire que vous n'utilisez jamais deux fois. Prenez 32 caractères hexadécimaux aléatoires.
  • X-Signature est expliqué dans Signer une commande. Il doit être en hexadécimal minuscule.

Les appels GET n'ont pas besoin de signature.

#Signer une commande

  1. Convertissez votre commande en texte JSON une seule fois. Ce sont exactement ces octets que vous signerez et enverrez.

  2. Calculez l'empreinte du corps : BODY_HASH = SHA-256 du corps, en hexadécimal minuscule.

  3. Assemblez ces cinq lignes avec un saut de ligne (\n), sans saut de ligne à la fin :

    Text
    POST
    /api/v1/orders
    <X-Timestamp>
    <X-Nonce>
    <BODY_HASH>
  4. Signez ce texte : X-Signature = HMAC-SHA256 de ce texte, avec votre clé API complète comme clé. Écrivez le résultat en hexadécimal minuscule.

L'erreur la plus fréquente consiste à signer une version du JSON et à en envoyer une autre. Par exemple, {"a":1} et {"a": 1} ne sont pas les mêmes octets. Vérifiez que votre bibliothèque HTTP ne modifie pas le corps après la signature.

Vecteur de test. Vérifiez votre code avec ces valeurs. La clé est fictive.

Text
API key      cvb2b_TEST_ONLY_not_a_real_key_0123456789abcdef
Method       POST
Path         /api/v1/orders
X-Timestamp  1790000000
X-Nonce      0123456789abcdef0123456789abcdef

Corps de la requête (108 octets, sur une ligne, sans saut de ligne à la fin) :

JSON
{"external_order_id":"TEST-0001","items":[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.2500"}]}

Résultats attendus :

Text
BODY_HASH    b0545ae25d54b219f27d8bd90e4dcbf26cf0d491f53982da67b8eef0a1a59960
X-Signature  fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfed

#Exemples de code

Tous les exemples donnent le résultat du vecteur de test. Chargez la clé depuis votre coffre à secrets, jamais depuis le code.

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"

Utilisez --data-raw et non -d @file. -d supprime les sauts de ligne : les octets envoyés ne correspondraient plus.

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 intégré)

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 (signature uniquement)

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

#Erreurs

Toutes ces erreurs renvoient HTTP 403 (et non 401), sauf la limite de requêtes, qui renvoie 429. Le corps de la réponse ressemble à ceci :

JSON
{"detail": "Invalid HMAC signature."}
ProblèmeQue faire
Merchant ID ou clé API absent ou incorrectVérifiez les deux en-têtes et l'environnement.
Accès API non activéAttendez que CardV valide votre entreprise.
L'IP de votre serveur n'est pas autoriséeAjoutez-la à la liste d'IP autorisées dans le Portail.
Endpoint réservé au PortailPassez par le Portail. Seuls sept endpoints fonctionnent avec une clé.
Signature absente ou incorrecteCorrigez votre code de signature. Testez-le avec le vecteur de test.
Horodatage trop ancien ou trop avancéSynchronisez l'horloge de votre serveur (NTP).
Nonce déjà utiliséUtilisez un nouveau nonce aléatoire à chaque commande.
Trop de requêtes (429)Attendez le nombre de secondes indiqué dans Retry-After, puis réessayez.

Important :

  • Quand vous renvoyez une commande, générez un nouvel horodatage, un nouveau nonce et une nouvelle signature. Gardez le même corps. Voir les renvois sans risque.
  • Un nonce est consommé même si la signature était fausse.
  • CardV enregistre les tentatives échouées et alerte son équipe si elles sont nombreuses.

#Liste d'IP autorisées

La liste d'IP autorisées réserve l'usage de votre clé à vos seuls serveurs. Vous la gérez dans le Portail (Owner).

  • Elle est facultative. Sans règle, les appels sont acceptés depuis n'importe quelle IP.
  • Dès qu'il existe une règle, les appels venant d'autres IP reçoivent HTTP 403.
  • Les règles ont la forme 203.0.113.10/32 (IPv4) ou 2001:db8::/48 (IPv6).
  • Elle ne concerne que les appels faits avec une clé API, pas la connexion au Portail.
  • Ajoutez toutes les IP de vos serveurs avant de l'activer, y compris les passerelles NAT et les régions de secours.

#Clés API

Une clé API donne accès exactement aux sept endpoints listés dans L'API en bref. Elle permet notamment de passer des commandes et de lire les codes : protégez-la comme le mot de passe d'un Owner.

Seul un Owner peut gérer les clés, dans le Portail, sous Integrations → API keys. Faites-le séparément en Live et en Sandbox.

  1. Créez une clé et donnez-lui un nom. Le Portail ne l'affiche pas encore.
  2. Affichez-la (Reveal). CardV vous envoie par e-mail un code à 6 chiffres, valable 10 minutes (5 essais). Saisissez-le pour voir la clé complète. Enregistrez-la tout de suite. Chaque affichage est journalisé.
  3. Désactivez une clé dont vous n'avez plus besoin. C'est immédiat et définitif.

Pour changer de clé sans interruption de service :

  1. Créez et affichez une nouvelle clé.
  2. Déployez-la sur tous vos serveurs.
  3. Vérifiez dans le Portail que l'ancienne clé n'est plus utilisée.
  4. Désactivez l'ancienne clé.

Changez de clé au moins une fois par an, et chaque fois qu'une personne qui y avait accès s'en va. Si une clé a pu fuiter, désactivez-la d'abord, puis cherchez la cause.

Une question sur votre intégration ? Écrivez à [email protected] en indiquant votre Merchant ID et l’identifiant de commande ou de requête.