Documentation API Authentification

Authentification

Chaque requête API doit être authentifiée par une signature. Cette page explique exactement comment.

Principe

GerminoPay utilise HMAC-SHA256 pour signer chaque requête. Contrairement aux clés API simples (qui peuvent être volées et réutilisées), HMAC signe le contenu de la requête avec votre secret, garantissant :

Clés API

Clé Format Utilisation
Public Key pk_live_xxxxxxxxxxxx Envoyée dans chaque requête via l'en-tête X-Public-Key
Secret Key sk_live_xxxxxxxxxxxx Jamais envoyée. Sert à calculer la signature HMAC.
Ne jamais exposer votre clé secrète Elle doit vivre uniquement sur votre serveur (variable d'environnement). Ne jamais la mettre dans du JavaScript côté client, du code mobile ou un dépôt Git.

Signature HMAC

Pour signer une requête, concaténez ces éléments dans cet ordre exact :

  1. La méthode HTTP en majuscules (GET, POST, etc.)
  2. Le chemin de la requête (sans le domaine) — ex. /v1/paiement/initier
  3. Le corps brut de la requête (JSON, exactement tel qu'envoyé)
  4. Le timestamp Unix (secondes)

Puis calculez :

Pseudo-code
signature = HMAC_SHA256(
    key    = secret_key,
    message = METHOD + PATH + BODY + TIMESTAMP
)
Important Le corps doit être exactement la même chaîne que celle envoyée. Même un espace en plus change la signature.

En-têtes obligatoires

En-tête Valeur Requis
Content-Type application/json Oui
X-Public-Key Votre clé publique Oui
X-Timestamp Timestamp Unix (secondes) Oui
X-Signature Signature HMAC-SHA256 en hexadécimal Oui

Exemples par langage

<?php
function germinoSign(string $method, string $path, string $body, string $secret): array
{
    $timestamp = time();
    $message = $method . $path . $body . $timestamp;
    $signature = hash_hmac("sha256", $message, $secret);

    return [
        "X-Timestamp" => $timestamp,
        "X-Signature" => $signature,
    ];
}

// Utilisation
$body = json_encode(["montant" => 5000, "devise" => "XOF"]);
$headers = germinoSign("POST", "/v1/paiement/initier", $body, getenv("GERMINOPAY_SECRET"));
const crypto = require('crypto');

function germinoSign(method, path, body, secret) {
  const timestamp = Math.floor(Date.now() / 1000);
  const message = `${method}${path}${body}${timestamp}`;
  const signature = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  return {
    'X-Timestamp': timestamp.toString(),
    'X-Signature': signature,
  };
}

// Usage
const body = JSON.stringify({ montant: 5000, devise: 'XOF' });
const headers = germinoSign('POST', '/v1/paiement/initier', body, process.env.GERMINOPAY_SECRET);
import hashlib
import hmac
import time

def germino_sign(method, path, body, secret):
    timestamp = int(time.time())
    message = f"{method}{path}{body}{timestamp}"
    signature = hmac.new(
        secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()

    return {
        "X-Timestamp": str(timestamp),
        "X-Signature": signature,
    }

# Usage
import json
body = json.dumps({"montant": 5000, "devise": "XOF"}, separators=(',', ':'))
headers = germino_sign("POST", "/v1/paiement/initier", body, os.environ["GERMINOPAY_SECRET"])
require 'openssl'
require 'time'

def germino_sign(method, path, body, secret)
  timestamp = Time.now.to_i.to_s
  message   = "#{method}#{path}#{body}#{timestamp}"
  signature = OpenSSL::HMAC.hexdigest('SHA256', secret, message)

  {
    'X-Timestamp' => timestamp,
    'X-Signature' => signature
  }
end

# Usage
body = '{"montant":5000,"devise":"XOF"}'
headers = germino_sign('POST', '/v1/paiement/initier', body, ENV['GERMINOPAY_SECRET'])
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class GerminoAuth {
    public static Map<String, String> sign(String method, String path, String body, String secret) throws Exception {
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String message = method + path + body + timestamp;

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));

        StringBuilder hex = new StringBuilder();
        for (byte b : hash) {
            hex.append(String.format("%02x", b));
        }

        Map<String, String> headers = new HashMap<>();
        headers.put("X-Timestamp", timestamp);
        headers.put("X-Signature", hex.toString());
        return headers;
    }
}
using System;
using System.Security.Cryptography;
using System.Text;
using System.Collections.Generic;

public class GerminoAuth
{
    public static Dictionary<string, string> Sign(
        string method, string path, string body, string secret)
    {
        var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
        var message = $"{method}{path}{body}{timestamp}";

        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
        var signature = BitConverter.ToString(hash).Replace("-", "").ToLower();

        return new Dictionary<string, string>
        {
            { "X-Timestamp", timestamp },
            { "X-Signature", signature }
        };
    }
}

Anti-replay

Comme le timestamp fait partie de la signature, un attaquant qui intercepte une requête ne peut pas la réutiliser. Toute requête avec un timestamp de plus de 5 minutes est automatiquement rejetée.

Synchronisation d'horloge L'horloge de votre serveur doit être synchronisée (NTP). Une dérive de plus de 5 minutes invalidera toutes vos requêtes.

Bonnes pratiques