Authentification
Chaque requête se signe elle-même en HMAC-SHA256 : aucun jeton à obtenir, à stocker ni à renouveler. Le secret ne quitte jamais votre serveur, seule la signature voyage.
Vos clés
Une clé est une paire : un identifiant (ipk_) qu’on envoie, et un secret (isk_) qui ne sert qu’à signer. Le secret s’affiche une seule fois, à la création de la clé dans votre espace marchand : rangez-le aussitôt dans votre gestionnaire de secrets.
| Usage | Identifiant | Hôte | Disponible |
|---|---|---|---|
| Sandbox public | ak_sandbox | sandbox.inopas.net | Tout de suite, sans compte. Partagées par tous. |
| Vos tests | ipk_sandbox_… | sandbox.inopas.net | Dès votre inscription, avant même la vérification du dossier. Vos données de test sont à vous seul. |
| Production | ipk_live_… | api.inopas.net | Une fois votre dossier vérifié. |
Les en-têtes
| En-tête | Valeur | Quand |
|---|---|---|
X-Inopas-Key | L’identifiant de votre clé. | Toujours |
X-Inopas-Timestamp | L’heure de l’envoi, en secondes depuis le 1er janvier 1970 (UTC). | Toujours |
X-Inopas-Signature | sha256= suivi de la signature en hexadécimal. | Toujours |
Idempotency-Key | Une valeur unique par opération, un UUID par exemple. | Écritures (POST) |
La chaîne à signer
Cinq parties, séparées par un point, dans cet ordre :
{horodatage}.{MÉTHODE}.{chemin}.{Idempotency-Key}.{corps}| Partie | Exemple | Règle |
|---|---|---|
| horodatage | 1790000000 | La valeur exacte de X-Inopas-Timestamp. |
| MÉTHODE | POST | En majuscules. |
| chemin | /v1/charges | Sans l’hôte ni la query string : /v1/charges, pas /v1/charges?limit=5. |
| Idempotency-Key | cmd-1042-essai-1 | La valeur exacte de l’en-tête. Vide pour une lecture. |
| corps | {"amount_minor":…} | Les octets exacts envoyés. Vide pour une lecture. |
Une lecture n’a ni clé d’idempotence ni corps : ses deux dernières parties sont vides et la chaîne se termine par deux points consécutifs, comme 1790000000.GET./v1/merchant..
La signature est le HMAC-SHA256 de cette chaîne avec votre secret, en hexadécimal minuscule, précédé de sha256=.
L’Idempotency-Key fait partie de la signature : une requête interceptée ne peut pas être rejouée sous une autre clé, et l’horodatage limite toute tentative à une fenêtre de 5 minutes.
Une fonction à copier
Elle renvoie les en-têtes à ajouter à la requête. Passez-lui le corps déjà sérialisé, puis envoyez exactement ce corps-là.
Aucune dépendance : le module crypto de Node et hmac de Python suffisent. En Bash, la fonction lit le secret dans la variable SECRET.
# signer METHODE CHEMIN IDEMPOTENCY_KEY CORPS
signer() {
local ts; ts=$(date +%s)
local sig; sig=$(printf '%s' "$ts.$1.$2.$3.$4" \
| openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.*= //')
echo "X-Inopas-Timestamp: $ts"
echo "X-Inopas-Signature: sha256=$sig"
}import { createHmac } from "node:crypto";
/**
* En-têtes d'authentification d'une requête /v1.
* `path` : sans l'hôte ni la query string. `body` : la chaîne EXACTE envoyée.
*/
export function entetesInopas({ keyId, secret, method, path, idempotencyKey = "", body = "" }) {
const ts = String(Math.floor(Date.now() / 1000));
const message = `${ts}.${method.toUpperCase()}.${path}.${idempotencyKey}.${body}`;
const sig = createHmac("sha256", secret).update(message).digest("hex");
return {
"X-Inopas-Key": keyId,
"X-Inopas-Timestamp": ts,
"X-Inopas-Signature": `sha256=${sig}`,
...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
};
}import hashlib, hmac, time
def entetes_inopas(key_id: str, secret: str, method: str, path: str,
idempotency_key: str = "", body: bytes = b"") -> dict[str, str]:
"""En-têtes d'authentification d'une requête /v1.
path : sans l'hôte ni la query string. body : les octets EXACTS envoyés.
"""
ts = str(int(time.time()))
message = f"{ts}.{method.upper()}.{path}.{idempotency_key}.".encode() + body
sig = hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
entetes = {
"X-Inopas-Key": key_id,
"X-Inopas-Timestamp": ts,
"X-Inopas-Signature": "sha256=" + sig,
}
if idempotency_key:
entetes["Idempotency-Key"] = idempotency_key
return entetesVérifier votre implémentation
Avant tout appel réseau, faites signer ces valeurs fixes par votre code. Il doit produire exactement la signature attendue. Sinon, l’écart est dans la construction de la chaîne, pas dans nos serveurs : comparez-la caractère par caractère.
Gardez ce vecteur dans vos tests unitaires : il vous prévient si une évolution de votre code casse la signature.
secret sk_sandbox_demo
horodatage 1790000000
méthode POST
chemin /v1/charges
Idempotency-Key cmd-1042-essai-1
corps {"amount_minor":2500000,"currency":"CDF","customer_ref":"+243810000000"}
chaîne signée
1790000000.POST./v1/charges.cmd-1042-essai-1.{"amount_minor":2500000,"currency":"CDF","customer_ref":"+243810000000"}
X-Inopas-Signature attendue
sha256=a2ae9d112027090cbfd88990f6ed97e78142c3b7520724eba98ddb441976ec74Diagnostiquer un 401
Un refus d’authentification répond toujours 401 avec le code unauthorized. Le message, lui, dit laquelle des vérifications a échoué :
| Message | Causes à vérifier |
|---|---|
| Authentification requise (clé API + signature). | Un des trois en-têtes X-Inopas-* manque ou est vide. |
| Authentification refusée : horodatage invalide. | X-Inopas-Timestamp n’est pas un nombre entier. |
| Authentification refusée : horodatage hors fenêtre (rejeu). | Plus de 5 minutes d’écart avec notre horloge. Le plus souvent : un horodatage en millisecondes (Date.now() sans division par 1000), ou un serveur sans synchronisation NTP. |
| Authentification refusée : clé inconnue ou inactive. | Clé révoquée, mal copiée, ou compte suspendu. |
| Cette clé de test ne fonctionne pas ici. | Une clé de test (ak_sandbox ou ipk_sandbox_…) a été envoyée à https://api.inopas.net. Tout le test vit sur https://sandbox.inopas.net : le message donne l’adresse. Avec le SDK, ne renseignez pas baseUrl, il choisit l’hôte d’après la clé. |
| Cette clé d’argent réel ne fonctionne pas sur l’environnement de test. | Une clé ipk_live_… a été envoyée à https://sandbox.inopas.net. Les clés réelles ne fonctionnent que sur https://api.inopas.net. |
| Authentification refusée : signature invalide. | La chaîne signée diffère de la requête reçue : corps re-sérialisé après la signature, query string incluse dans le chemin, Idempotency-Key signée différente de celle envoyée, préfixe sha256= absent, ou secret d’un autre environnement. |