---
title: "Vérifier la signature des notifications de rendez-vous"
description: "Récupérer le secret d'un webhook et vérifier, en PHP ou en JavaScript, que la notification reçue vient bien d'Apenday."
url: "https://support.apenday.com/support/verifier-la-signature-des-notifications-de-rendez-vous/"
updated: "2026-09-29"
category: Développeurs
---

# Vérifier la signature des notifications de rendez-vous

Apenday signe les notifications envoyées à votre système. La signature vous permet de vérifier que l'appel vient bien d'Apenday et que son contenu n'a pas été modifié en chemin.

## Récupérer le secret

Le secret est propre à chaque webhook et n'est affiché qu'une seule fois.

1. Depuis l'onglet **Avancé** de votre profil utilisateur, cliquez sur **Ajouter un webhook**.
2. Après l'enregistrement, la fenêtre **Secret de signature du webhook** affiche le secret.
3. Copiez-le, enregistrez-le dans votre système, puis cliquez sur **J'ai conservé ce secret**.

Le secret est une chaîne de 64 caractères hexadécimaux. Il n'est plus consultable une fois la fenêtre fermée. Par l'API, il est renvoyé dans la réponse de la souscription, voir [Utiliser les webhooks Apenday](https://support.apenday.com/support/utiliser-les-webhook-apenday/).

## Les en-têtes

| En-tête | Contenu |
| --- | --- |
| `X-Apenday-Timestamp` | Heure de la tentative d'envoi, en secondes écoulées depuis le 1er janvier 1970, en UTC. Exemple : `1789480000` |
| `X-Apenday-Event-Id` | Identifiant unique de l'événement. Exemple : `3f6c1a2e-8d4b-4c7e-9a51-0b2e7d9c4f10` |
| `X-Apenday-Event` | L'événement notifié. Exemple : `meeting_confirmed` |
| `X-Apenday-Event-Timestamp` | Moment de l'événement, en secondes écoulées depuis le 1er janvier 1970, en UTC. Exemple : `1789479700` |
| `X-Apenday-Signature` | Le préfixe `v1=` suivi de la signature, en hexadécimal minuscule. Exemple : `v1=3a5f1c9e...` |

`X-Apenday-Timestamp` et `X-Apenday-Signature` ne sont présents que sur un webhook signé. Les trois autres sont présents sur toutes les notifications.

## La chaîne signée

La signature est un HMAC-SHA256 calculé sur les valeurs de quatre en-têtes puis le corps de la requête, séparés par un point, dans cet ordre :

```
{X-Apenday-Timestamp}.{X-Apenday-Event-Id}.{X-Apenday-Event}.{X-Apenday-Event-Timestamp}.{corps}
```

Exemple :

```
1789480000.3f6c1a2e-8d4b-4c7e-9a51-0b2e7d9c4f10.meeting_confirmed.1789479700.{"@context":"/api/contexts/Appointment","@id":"/api/public/appointments/...
```

Trois points font échouer la plupart des premières intégrations.

**Signez le corps brut.** Si votre code désérialise le corps puis le resérialise avant de calculer la signature, vous obtenez une valeur différente. Lisez le corps tel qu'il arrive, et reprenez les valeurs des en-têtes telles que reçues.

**Utilisez le secret tel quel.** C'est une chaîne hexadécimale, mais elle sert de clé sous sa forme texte. Ne la convertissez pas en binaire avant de calculer le HMAC.

**Comparez en temps constant**, avec `hash_equals` en PHP ou `crypto.timingSafeEqual` en JavaScript. Jamais `==` ni `===`.

## Vérifier en PHP

```
$secret = 'votre-secret';

$timestamp = $_SERVER['HTTP_X_APENDAY_TIMESTAMP'] ?? '';
$eventId = $_SERVER['HTTP_X_APENDAY_EVENT_ID'] ?? '';
$event = $_SERVER['HTTP_X_APENDAY_EVENT'] ?? '';
$eventTimestamp = $_SERVER['HTTP_X_APENDAY_EVENT_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_APENDAY_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');

// Rejeter un appel trop ancien. Cinq minutes est une tolérance raisonnable.
if ('' === $timestamp || abs(time() - (int) $timestamp) > 300) {
    http_response_code(403);
    exit;
}

$signed = implode('.', [$timestamp, $eventId, $event, $eventTimestamp, $body]);
$expected = 'v1=' . hash_hmac('sha256', $signed, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(403);
    exit;
}

// La notification est authentique.
$appointment = json_decode($body, true);

http_response_code(200);
```

## Vérifier en JavaScript

Avec Express, `express.json()` a déjà consommé et reformaté le corps au moment où votre code s'exécute. Conservez le corps brut avec l'option `verify`.

```
const crypto = require('crypto');
const express = require('express');

const SECRET = 'votre-secret';
const app = express();

app.use(express.json({
    verify: (req, res, buf) => {
        req.rawBody = buf.toString('utf8');
    },
}));

app.post('/apenday/webhook', (req, res) => {
    const timestamp = req.get('X-Apenday-Timestamp') || '';
    const eventId = req.get('X-Apenday-Event-Id') || '';
    const event = req.get('X-Apenday-Event') || '';
    const eventTimestamp = req.get('X-Apenday-Event-Timestamp') || '';
    const signature = req.get('X-Apenday-Signature') || '';

    // Rejeter un appel trop ancien. Cinq minutes est une tolérance raisonnable.
    if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
        return res.sendStatus(403);
    }

    const signed = [timestamp, eventId, event, eventTimestamp, req.rawBody].join('.');
    const expected = 'v1=' + crypto
        .createHmac('sha256', SECRET)
        .update(signed)
        .digest('hex');

    const received = Buffer.from(signature);
    const computed = Buffer.from(expected);

    if (received.length !== computed.length || !crypto.timingSafeEqual(received, computed)) {
        return res.sendStatus(403);
    }

    // La notification est authentique.
    res.sendStatus(200);
});
```

`crypto.timingSafeEqual` lève une exception si les deux valeurs n'ont pas la même longueur. La comparaison des longueurs avant l'appel n'est donc pas optionnelle.

## En cas de perte du secret

Ouvrez le webhook et cliquez sur **Régénérer le secret**. L'ancien secret cesse immédiatement d'être valable : installez le nouveau dans votre système avant de reprendre la vérification.

## Ce que votre système doit prévoir

**Une relance porte son propre horodatage.** `X-Apenday-Timestamp` et la signature sont recalculés à chaque tentative. Une relance peut arriver une trentaine de minutes après l'événement, mais votre contrôle d'ancienneté n'a pas à être élargi pour l'accepter.

**Les en-têtes d'événement ne changent pas d'une tentative à l'autre.** `X-Apenday-Event-Id` et `X-Apenday-Event-Timestamp` servent à dédoublonner et à ordonner les notifications, comme décrit dans [Utiliser les webhooks Apenday](https://support.apenday.com/support/utiliser-les-webhook-apenday/), section « Répondre à une notification ».
