Tchin Docs

Aller plus loin

Webhooks & signature

Un paiement mobile money n'est jamais instantané : vous envoyez la demande, le client valide sur son téléphone. C'est le webhook qui vous dit ce qui s'est réellement passé. C'est la seule source à laquelle vous devez faire confiance pour livrer une commande.

Ne livrez jamais sur la seule réponse d'un appel API. Cette réponse dit « la demande est partie », pas « l'argent est arrivé ». Attendez status: completed dans un webhook signé, ou vérifiez le statut vous-même.

À quoi ressemble un appel

Nous envoyons un POST en application/x-www-form-urlencoded vers votre callback_url, avec les données sous la clé data. La signature est présente deux fois — en en-tête et dans le corps — utilisez celle qui vous arrange.

HTTP
POST /votre-webhook HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Tchin-Timestamp: 1786000000
Tchin-Signature: v1=b7ff5b2a4992bfb4855ae788d1c0a3f9e6c24b81f0d7a5e39c4b2168ad0f7e52

data[reference]=8f2ad91c&data[token]=inv_9d3f…&data[status]=completed
&data[amount]=18500&data[fee]=740&data[net]=17760&data[mode]=live
&data[timestamp]=1786000000&data[signature]=b7ff5b2a…

Vérifier la signature

Votre URL de webhook est publique : n'importe qui peut vous envoyer un faux « paiement réussi ». La signature est ce qui distingue un appel venant de nous d'un appel venant d'un inconnu. Elle est calculée avec votre clé privée, que vous seul et nous connaissons.

La chaîne signée est la concaténation, séparée par des points, de sept valeurs dans cet ordre exact :

timestamp . reference . token . status . amount . net . mode

Puis : HMAC-SHA256(chaîne, votre_clé_privée), en hexadécimal minuscule. Modifier un seul de ces champs — le montant, le statut, la référence — invalide la signature.

En PHP

PHP
<?php
// Vérification d'un webhook Tchin — à faire AVANT toute écriture en base.

$secret = getenv('TCHIN_SECRET_KEY');   // votre clé privée, jamais dans le code
$d      = $_POST['data'] ?? [];

// 1. L'appel est-il récent ? (on refuse au-delà de 5 minutes)
$ts = (int) ($_SERVER['HTTP_TCHIN_TIMESTAMP'] ?? $d['timestamp'] ?? 0);
if (abs(time() - $ts) > 300) {
    http_response_code(400);
    exit('horodatage hors fenêtre');
}

// 2. La signature correspond-elle au contenu reçu ?
$chaine = implode('.', [
    $ts,
    $d['reference'] ?? '',
    $d['token']     ?? '',
    $d['status']    ?? '',
    $d['amount']    ?? '',
    $d['net']       ?? '',
    $d['mode']      ?? '',
]);
$attendue = hash_hmac('sha256', $chaine, $secret);

// hash_equals : comparaison à temps constant, indispensable ici
$recue = $d['signature'] ?? '';
if (! hash_equals($attendue, $recue)) {
    http_response_code(403);
    exit('signature invalide');
}

// 3. Seulement maintenant, on traite — et une seule fois par token.
if (($d['status'] ?? '') === 'completed' && ! commandeDejaPayee($d['token'])) {
    marquerPayee($d['token'], (int) $d['net']);
}

http_response_code(200);
echo 'ok';

En Node.js

JavaScript
// Express — vérification d'un webhook Tchin
const crypto = require('crypto');

app.post('/webhook/tchin', express.urlencoded({ extended: true }), (req, res) => {
  const d = req.body.data || {};
  const secret = process.env.TCHIN_SECRET_KEY;

  const ts = Number(req.get('Tchin-Timestamp') || d.timestamp || 0);
  if (Math.abs(Date.now() / 1000 - ts) > 300) return res.status(400).end('horodatage hors fenêtre');

  const chaine = [ts, d.reference, d.token, d.status, d.amount, d.net, d.mode].join('.');
  const attendue = crypto.createHmac('sha256', secret).update(chaine).digest('hex');

  const a = Buffer.from(attendue), b = Buffer.from(String(d.signature || ''));
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.status(403).end('signature invalide');

  if (d.status === 'completed') livrerCommande(d.token, Number(d.net));
  res.status(200).end('ok');
});

Trois réflexes. Comparez la signature avec une fonction à temps constant (hash_equals, timingSafeEqual) — un == ordinaire laisse fuir l'information. Refusez les horodatages de plus de 5 minutes, sinon un appel intercepté peut être rejoué. Et traitez chaque token une seule fois : un webhook peut arriver en double, c'est normal.

Le champ hash

Les anciennes intégrations lisaient un champ hash. Il est toujours envoyé, mais il ne prouve rien : c'est une valeur constante, identique sur tous vos webhooks, donc reproductible par quiconque en a vu un seul. Ne l'utilisez plus comme preuve d'authenticité — basculez sur signature.

Ce que nous envoyons

ChampContenu
statuscompleted · pending · failed · cancelled
referenceLe token du lien ou de l'abonnement concerné.
tokenIdentifiant unique de la transaction. Votre clé d'idempotence.
amount · fee · netPayé par le client · frais Tchin · crédité sur votre solde.
country · methodPays et opérateur utilisés.
customerNom, email et téléphone, quand ils sont connus.
modelive ou test. Ne livrez rien sur du test.
timestamp · signatureCe qui permet la vérification ci-dessus.

Les abonnements ajoutent un champ event et un objet subscription — voir la page Abonnements.

Si le webhook n'arrive pas

Un webhook peut se perdre : votre serveur redémarre, le réseau coupe, votre pare-feu bloque. Nous ne vous laissons pas seul face à ça.

De notre côté : une vérification toutes les 5 minutes

Un service interroge PayDunya toutes les cinq minutes sur toutes les transactions restées en attente, met leur statut à jour, crédite ce qui doit l'être et vous renvoie le webhook. Vous n'avez rien à demander : si un paiement a réellement abouti, vous finirez par être prévenu.

Un panier jamais payé — le client a ouvert la page et n'a rien validé — est clôturé au bout de deux heures et passe en cancelled.

De votre côté : interrogez le statut

C'est la ceinture en plus des bretelles, et c'est recommandé : quand un client revient sur votre site après un paiement, vérifiez vous-même plutôt que de faire confiance à l'URL de retour.

cURL
curl -H "TCHIN-PUBLIC-KEY: tchin_pk_xxxxxxxx" \
     -H "TCHIN-PRIVATE-KEY: tchin_sk_xxxxxxxx" \
  https://tchin.tech/api/v1/payments/inv_9d3f7a21c4/status
GET /api/v1/payments/REMPLACEZ_PAR_VOTRE_TOKEN/status Essayer
Vos clés se trouvent dans votre espace, onglet API.

Utilisez vos clés de test : en mode test aucun argent ne circule. Vos clés restent dans ce navigateur — elles ne sont ni enregistrées ni transmises à un tiers. Dans votre intégration réelle, les clés doivent rester sur votre serveur, jamais dans une page.

Ce que nous attendons de votre endpoint

  • En HTTPS et joignable publiquement. Nous refusons les adresses internes et le HTTP simple.
  • Répondez 200 rapidement. Faites le travail lourd après avoir répondu.
  • Supportez les doublons : même token deux fois = un seul traitement.
  • Ne vous fiez pas à l'ordre d'arrivée : un pending peut suivre un completed.

Tester sans argent

En mode test, créez un paiement puis réglez-le avec un compte PayDunya de test : vous recevez les mêmes webhooks, avec la même signature, et GET /payments/{token}/status suit le paiement exactement comme en réel. Simplement, aucun solde n'est crédité. Le champ mode vaut test — votre code doit s'en servir pour ne rien livrer pour de vrai.

Base API https://tchin.tech/api/v1 Retour au sommaire

Bienvenue.

Votre adresse suffit, le compte se crée seul.

Votre code

Six chiffres envoyés à

Vous acceptez nos conditions et politiques.