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
| Identifiant | Exemple | Secret ? |
|---|---|---|
| Merchant ID | M00000001 | Non. Il indique qui vous êtes. |
| Clé API | cvb2b_... | 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/accountaffiche alors"api_access_enabled": true.
#En-têtes
Envoyez ces deux en-têtes à chaque appel :
X-Merchant-Id: M00000001
X-Api-Key: cvb2b_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxSur POST/orders, envoyez aussi ces trois-là :
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfedX-Timestampest 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-Nonceest une valeur aléatoire que vous n'utilisez jamais deux fois. Prenez 32 caractères hexadécimaux aléatoires.X-Signatureest expliqué dans Signer une commande. Il doit être en hexadécimal minuscule.
Les appels GET n'ont pas besoin de signature.
#Signer une commande
Convertissez votre commande en texte JSON une seule fois. Ce sont exactement ces octets que vous signerez et enverrez.
Calculez l'empreinte du corps :
BODY_HASH= SHA-256 du corps, en hexadécimal minuscule.Assemblez ces cinq lignes avec un saut de ligne (
\n), sans saut de ligne à la fin :POST /api/v1/orders <X-Timestamp> <X-Nonce> <BODY_HASH>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.
API key cvb2b_TEST_ONLY_not_a_real_key_0123456789abcdef
Method POST
Path /api/v1/orders
X-Timestamp 1790000000
X-Nonce 0123456789abcdef0123456789abcdefCorps de la requête (108 octets, sur une ligne, sans saut de ligne à la fin) :
{"external_order_id":"TEST-0001","items":[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.2500"}]}Résultats attendus :
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)
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)
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é)
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
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 :
{"detail": "Invalid HMAC signature."}| Problème | Que faire |
|---|---|
| Merchant ID ou clé API absent ou incorrect | Vé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ée | Ajoutez-la à la liste d'IP autorisées dans le Portail. |
| Endpoint réservé au Portail | Passez par le Portail. Seuls sept endpoints fonctionnent avec une clé. |
| Signature absente ou incorrecte | Corrigez 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) ou2001: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.
- Créez une clé et donnez-lui un nom. Le Portail ne l'affiche pas encore.
- 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é.
- 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 :
- Créez et affichez une nouvelle clé.
- Déployez-la sur tous vos serveurs.
- Vérifiez dans le Portail que l'ancienne clé n'est plus utilisée.
- 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.