> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mercy-pay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sécurité des webhooks

> Vérifiez la signature de chaque requête avant de la traiter.

Une URL de webhook est publique : n'importe qui peut lui envoyer une fausse requête. Vérifiez donc la signature avant de traiter un événement.

## Principe

Chaque requête porte l'en-tête `MercyPay-Signature` :

```http theme={"theme":{"light":"github-light","dark":"github-dark"}}
MercyPay-Signature: t=1791022800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

* `t` est l'heure d'envoi, en secondes depuis 1970 ;
* `v1` est le **HMAC-SHA256** en hexadécimal de la chaîne `{t}.{corps brut}`, calculé avec le secret de l'endpoint.

Pour vérifier :

1. Lisez le corps **tel qu'il est reçu**, en octets, sans le parser.
2. Refusez la requête si `t` a plus de 5 minutes d'écart avec votre horloge : une requête ancienne rejouée est ainsi rejetée.
3. Calculez le HMAC-SHA256 de `{t}.{corps brut}` avec votre secret.
4. Comparez-le à temps constant avec chaque valeur `v1` de l'en-tête. Une seule doit correspondre.

## Exemple en PHP

```php theme={"theme":{"light":"github-light","dark":"github-dark"}}
<?php

$rawBody = file_get_contents('php://input');
$header = $_SERVER['HTTP_MERCYPAY_SIGNATURE'] ?? '';
$secret = getenv('MERCYPAY_WEBHOOK_SECRET');

$timestamp = null;
$signatures = [];

foreach (explode(',', $header) as $part) {
    [$key, $value] = array_pad(explode('=', trim($part), 2), 2, '');

    if ($key === 't') {
        $timestamp = (int) $value;
    } elseif ($key === 'v1') {
        $signatures[] = $value;
    }
}

$expected = hash_hmac('sha256', $timestamp.'.'.$rawBody, $secret);
$valid = $timestamp && abs(time() - $timestamp) <= 300
    && array_filter($signatures, fn ($signature) => hash_equals($expected, $signature)) !== [];

if (! $valid) {
    http_response_code(401);
    exit;
}

$event = json_decode($rawBody, true);
```

Le même contrôle en JavaScript et en Python est dans [Gérer les webhooks](https://docs.mercy-pay.com/developers/webhooks/handle.md).

## Autres en-têtes

| En-tête | Contenu |
| - | - |
| `MercyPay-Event-Id` | L'`id` de l'événement (`evt_…`), pour dédupliquer |
| `MercyPay-Event-Type` | Son type, par exemple `payment.succeeded` |
| `MercyPay-Delivery-Id` | L'identifiant de cette livraison (`whd_…`), le même à chaque nouvelle tentative |

## Changer de secret

Régénérez le secret depuis l'onglet **Webhooks** de la [console Développeurs](https://docs.mercy-pay.com/developers/console.md), ou avec [`POST /api/v1/webhook_endpoints/{id}/rotate_secret`](https://docs.mercy-pay.com/api/webhook-endpoints/rotate-secret). Pendant 24 heures, chaque webhook porte deux `v1` : un calculé avec le nouveau secret, un avec l'ancien. Votre serveur continue donc d'accepter les webhooks, quel que soit le secret qu'il utilise, le temps de déployer le nouveau.

## Bonnes pratiques

* Gardez le secret dans votre gestionnaire de secrets, jamais dans le code.
* Refusez tout événement dont la signature ne correspond pas.
* Gardez votre horloge à l'heure (NTP) : la tolérance est de 5 minutes.
* Ne vous fiez pas à l'ordre d'arrivée : utilisez le `status` de la ressource.
* Si un secret fuite, régénérez-le sans attendre.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.