# Recevoir des webhooks signés

> La passerelle livre les évènements de votre tenant à votre URL, signés au format Standard Webhooks. Vérifiez la signature avant tout traitement. Côté produit…

Guide - Depuis la version 1.5.0 - Flexway v1.6.3 - https://flexway.io/docs/developer-webhooks/

> La passerelle livre les évènements de votre tenant à votre URL, signés au format **Standard Webhooks**. Vérifiez la signature avant tout traitement.
>
> Côté produit : [Plan asynchrone et courtiers](https://flexway.io/docs/product-async/) ; côté exploitation : [Publier et consommer sur un courtier](https://flexway.io/docs/operations-async/).

## Ce que vous recevez

| Élément | Valeur |
| --- | --- |
| Méthode et corps | `POST`, un CloudEvent JSON (`Content-Type: application/cloudevents+json`) |
| `webhook-id` | L'identifiant de l'évènement (`id` du CloudEvent), stable entre deux tentatives |
| `webhook-timestamp` | Secondes Unix de la tentative |
| `webhook-signature` | `v1,<base64>` ; deux valeurs séparées par une espace pendant une rotation du secret |
| Secret | `whsec_<base64>`, remis par l'exploitant |

La signature est `HMAC-SHA256(secret décodé, "<webhook-id>.<webhook-timestamp>.<corps brut>")` en base64.

## Vérifier

```python
import base64, hashlib, hmac, time
def verify(secret: str, headers, body: bytes, tolerance=300) -> bool:
    key = base64.b64decode(secret.removeprefix("whsec_"))
    msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > tolerance:
        return False
    mac = base64.b64encode(hmac.new(key, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256).digest()).decode()
    return any(hmac.compare_digest(mac, s.split(",", 1)[1]) for s in headers["webhook-signature"].split())
```

```go
func verify(secret string, h http.Header, body []byte) bool {
	key, _ := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, "whsec_"))
	m := hmac.New(sha256.New, key)
	fmt.Fprintf(m, "%s.%s.", h.Get("webhook-id"), h.Get("webhook-timestamp"))
	m.Write(body)
	want := base64.StdEncoding.EncodeToString(m.Sum(nil))
	for _, s := range strings.Fields(h.Get("webhook-signature")) {
		if v, ok := strings.CutPrefix(s, "v1,"); ok && hmac.Equal([]byte(v), []byte(want)) {
			return true
		}
	}
	return false // vérifiez aussi l'écart de webhook-timestamp
}
```

Les bibliothèques officielles Standard Webhooks (`standardwebhooks` en Python, JavaScript, Go...)
font la même vérification. Lisez le corps **brut**, avant tout décodage JSON.

## Répondre

| Votre réponse | Ce que fait la passerelle |
| --- | --- |
| `2xx` | Livré |
| `408`, `429`, `5xx`, délai, connexion refusée | Nouvelle tentative, en respectant votre `Retry-After` (plafonné) |
| Autre `4xx` | Pas de nouvelle tentative |
| Tentatives épuisées | L'évènement part en file de rebut de l'exploitant |

Répondez vite et traitez ensuite ; dédupliquez sur `webhook-id` : la livraison est **au moins une fois**.
