Webhooks
Recevez des notifications en temps réel lorsqu'une transaction change de statut. Chaque notification est signée HMAC-SHA256 pour garantir son authenticité.
Configuration
Configurez votre URL webhook lors de la génération de votre clé API ou via le dashboard marchand. Votre endpoint doit :
- Répondre en HTTPS
- Retourner HTTP 200 en moins de 10 secondes
- Vérifier la signature
X-Signature
Types d'événements
| Événement | Déclencheur |
|---|---|
payin.success | Paiement entrant confirmé |
payin.failed | Paiement entrant refusé ou timeout |
payin.pending | Paiement entrant en attente de confirmation |
payout.success | Virement sortant confirmé |
payout.failed | Virement sortant échoué |
Format du payload
{
"event": "payin.success",
"transaction_id": "PAY_XYZABC123456_1719100000",
"provider": "jeko",
"provider_ref": "d22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc",
"status": "success",
"message": "Payment confirmed.",
"action_url": null,
"timestamp": "2024-06-23T12:00:00+00:00"
}
Vérification de la signature
Chaque requête webhook inclut le header X-Signature avec une signature HMAC-SHA256 du payload :
X-Signature: sha256=a1b2c3d4e5f6...
X-Event: payin.success
Vérification en PHP
public function handleWebhook(Request $request): JsonResponse
{
$secret = env('ENVOIFACILE_WEBHOOK_SECRET');
$payload = $request->getContent();
$signature = $request->header('X-Signature');
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature ?? '')) {
return response()->json(['error' => 'Invalid signature'], 401);
}
$data = $request->json()->all();
$event = $data['event'];
$trxId = $data['transaction_id'];
match ($event) {
'payin.success' => $this->onPayinSuccess($trxId, $data),
'payout.success' => $this->onPayoutSuccess($trxId, $data),
'payin.failed', 'payout.failed' => $this->onPaymentFailed($trxId, $data),
default => null,
};
return response()->json(['received' => true]);
}
Vérification en Node.js
const crypto = require('crypto');
app.post('/webhook/envoifacile', express.raw({ type: 'application/json' }), (req, res) => {
const secret = process.env.ENVOIFACILE_WEBHOOK_SECRET;
const signature = req.headers['x-signature'];
const payload = req.body;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).json({ error: 'Invalid signature' });
}
const data = JSON.parse(payload);
console.log(`Event: ${data.event} | Trx: ${data.transaction_id}`);
res.json({ received: true });
});
Utilisez toujours hash_equals ou timingSafeEqual
Ne comparez jamais les signatures avec == ou ===. Ces opérateurs sont vulnérables aux attaques timing. Utilisez hash_equals() (PHP) ou crypto.timingSafeEqual() (Node.js).
Politique de retry
Si votre endpoint ne répond pas HTTP 200 dans les 10 secondes, nous retentons automatiquement :
| Tentative | Délai | Statut webhook |
|---|---|---|
| 1ère (immédiate) | 0s | pending |
| 2ème | +60s | pending |
| 3ème (finale) | +5min | abandoned si échec |
Idempotence de votre endpoint
Votre endpoint peut recevoir le même événement plusieurs fois (retries). Utilisez le transaction_id comme clé d'idempotence côté réception.