Démarrage rapide
Cinq étapes, environ dix minutes. À la fin, vous avez fait votre premier appel facturé contre la production.
-
Ce que ExaTrust vous a envoyé
Vous avez reçu de votre référent commercial :
- Un identifiant de clé
key_idau formatpk_<slug>_<6hex>— exemple :pk_orange_8a3b. - L’URL de base à utiliser :
https://partner.exatrust.cg/v1. - Optionnellement, pour les partenaires mTLS :
ca.crt(notre autorité de certification),<slug>.crt(votre certificat client) et<slug>.key(la clé privée associée).
La clé privée Ed25519 utilisée pour signer les requêtes, c’est vous qui la générez à l’étape suivante. ExaTrust n’a jamais accès à votre clé privée — seulement à votre clé publique.
- Un identifiant de clé
-
Générer votre keypair Ed25519
Une commande
opensslsuffit. Stockez ces deux fichiers à un emplacement sécurisé (gestionnaire de secrets, vault, dossierchmod 600).Fenêtre de terminal openssl genpkey -algorithm ed25519 -out partner.priv.pemopenssl pkey -in partner.priv.pem -pubout -out partner.pub.pemTransmettez uniquement
partner.pub.pemà votre référent ExaTrust (gestionnaire de secrets, message chiffré, jamais par email en clair). Sous 24 h votrekey_idest actif côté serveur. -
Signer une requête (RFC 9421)
Chaque appel doit porter deux en-têtes :
Signature-Input(la liste des champs signés et les métadonnées) etSignature(la signature Ed25519 en base64). Champs signés recommandés :@method,@path,@query. Métadonnées requises :created,keyid,alg=ed25519,nonce.main.go package mainimport ("crypto/ed25519""fmt""io""net/http""github.com/remitly-oss/httpsig-go""github.com/remitly-oss/httpsig-go/keyutil""github.com/remitly-oss/httpsig-go/sigtypes")func main() {priv, err := keyutil.ReadPrivateKeyFile("partner.priv.pem")if err != nil {panic(err)}req, _ := http.NewRequest("GET","https://partner.exatrust.cg/v1/me", nil)if err := httpsig.Sign(req,httpsig.SigningProfile{Algorithm: sigtypes.Algo_ED25519,Fields: httpsig.Fields("@method", "@path", "@query"),Metadata: []httpsig.Metadata{httpsig.MetaCreated,httpsig.MetaKeyID,httpsig.MetaAlgorithm,httpsig.MetaNonce,},Nonce: httpsig.NonceRandom32,Label: "sig1",},httpsig.SigningKey{Key: priv.(ed25519.PrivateKey),MetaKeyID: "pk_orange_8a3b",},); err != nil {panic(err)}resp, err := http.DefaultClient.Do(req)if err != nil {panic(err)}defer resp.Body.Close()body, _ := io.ReadAll(resp.Body)fmt.Printf("%d %s\n%s\n", resp.StatusCode, resp.Status, body)}me.py import requestsfrom http_message_signatures import (HTTPSignatureKeyResolver, HTTPMessageSigner, algorithms,)from cryptography.hazmat.primitives.serialization import load_pem_private_keyclass StaticResolver(HTTPSignatureKeyResolver):def __init__(self, path: str):with open(path, "rb") as f:self.priv = load_pem_private_key(f.read(), password=None)def resolve_private_key(self, key_id: str):return self.privprepped = requests.Request("GET", "https://partner.exatrust.cg/v1/me").prepare()HTTPMessageSigner(signature_algorithm=algorithms.ED25519,key_resolver=StaticResolver("partner.priv.pem"),).sign(prepped,key_id="pk_orange_8a3b",covered_component_ids=("@method", "@path", "@query"),label="sig1",)resp = requests.Session().send(prepped)print(resp.status_code, resp.text)me.mjs import { readFileSync } from "node:fs";import {createSigner,signMessage,} from "http-message-signatures";const url = "https://partner.exatrust.cg/v1/me";const signer = createSigner(readFileSync("partner.priv.pem"),"ed25519","pk_orange_8a3b",);const signed = await signMessage({key: signer,fields: ["@method", "@path", "@query"],name: "sig1",params: ["created", "keyid", "alg", "nonce"],},{ method: "GET", url, headers: {} },);const resp = await fetch(url, { headers: signed.headers });console.log(resp.status, await resp.text());me.sh # curl ne peut pas générer la signature lui-même.# Utilisez l'outil ExaTrust `signtest` (binaire Go fourni) pour exporter les en-têtes,# puis injectez-les dans curl.eval "$(signtest \-url 'https://partner.exatrust.cg/v1/me' \-priv partner.priv.pem \-kid pk_orange_8a3b \-export)"curl -sS \-H "Signature-Input: $SIG_INPUT" \-H "Signature: $SIG" \https://partner.exatrust.cg/v1/meDétails complets de la signature (base de signature, format des en-têtes, gestion mTLS) sur la page Authentification.
-
Premier appel :
GET /v1/meCet endpoint ne consomme aucun crédit. Il sert à valider que votre signature est correcte et à voir votre solde.
Réponse attendue (200) :
{"user_id": 42,"email": "tech@orange.cg","key_id": "pk_orange_8a3b","credits": {"mode": "prepaid","balance": 5000,"total_consumed": 0}}Si vous voyez un
200et votre solde, l’intégration cryptographique est fonctionnelle. Si vous obtenez401 INVALID_SIGNATURE, vérifiez que votre clé publique a bien été remontée côté serveur, que votre horloge est synchronisée (NTP, fenêtre ±5 min), et que le label de la signature est biensig1. -
Premier appel facturé :
GET /v1/resultats/candidats/EANB260094Reprenez votre code et changez simplement l’URL. Cet appel consomme 1 crédit sur votre solde — y compris si le matricule n’existe pas (la requête est exécutée).
Fenêtre de terminal GET https://partner.exatrust.cg/v1/resultats/candidats/EANB260094Réponse attendue (200) :
{"matricule": "EANB260094","session_id": 26,"session_name": "2025","examen_id": 3,"examen_name": "BEPC","candidat": {"nom": "LOUMOUAMOU","prenom": "Mignon Marcel Petith","date_naissance": "2009-09-30","sexe": "M","mention": "AB","serie": null},"etablissement": {"code": "ETS001","name": "École Charles Mynyngou","region": "Brazzaville"},"meta": {"request_id": "01HXY...","credit_mode": "prepaid","credits_remaining": 4285}}Notez le
meta.credits_remaining: votre solde décrémenté en temps réel. Notez aussi lemeta.request_id: c’est l’identifiant à fournir au support ExaTrust en cas de dispute ou d’incident.
Et après
- Lister les deux endpoints et leurs réponses détaillées
- Comprendre la signature RFC 9421 et le mTLS avec snippets dans quatre langages
- Gérer les codes d’erreur, le rate-limit et les crédits