Webhooks
Plutôt que d'interroger l'API en boucle, faites-vous prévenir.
Les quatre événements
| Événement | Quand il part |
|---|---|
model.trained | une version vient d'être entraînée et sert les prédictions |
model.stale | assez de vérités se sont accumulées, une intégration serait utile |
model.refreshed | une intégration a produit une nouvelle version |
credits.low | votre solde de crédits approche de zéro |
Déclarer une adresse
curl -X POST "$SPAVIK_BASE_URL/v1/webhooks" \
-H "X-API-Key: $SPAVIK_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url":"https://exemple.fr/webhooks/spavik","events":["model.stale","credits.low"]}'Le secret n'apparaît qu'une fois
La réponse à cette création contient un champ secret. C'est la seule fois où il est affiché. Rangez-le immédiatement dans votre gestionnaire de secrets : il n'existe aucun moyen de le relire ensuite.
Une même adresse ne peut être déclarée qu'une fois. Une seconde tentative reçoit 409 WEBHOOK_URL_EXISTS, avec le nom du webhook qui occupe déjà la place.
Vérifier la signature
Chaque appel est signé. Vérifiez-le avant de faire quoi que ce soit de son contenu : sans cette vérification, n'importe qui connaissant votre URL peut vous faire croire n'importe quoi.
from spavik.webhooks import verifier, SpavikSignatureError
@app.post("/webhooks/spavik")
async def recevoir(request):
try:
evenement = verifier(await request.body(), request.headers, SECRET)
except SpavikSignatureError:
return Response(status_code=400)
traiter(evenement)
return Response(status_code=200)app.post('/webhooks/spavik', express.raw({ type: 'application/json' }), async (req, res) => {
try {
const evenement = await verifier(req.body, req.headers, process.env.SPAVIK_WEBHOOK_SECRET);
traiter(evenement);
res.sendStatus(200);
} catch {
res.sendStatus(400);
}
});app.post('/webhooks/spavik', async (c) => {
const corps = await c.req.arrayBuffer();
const evenement = await verifier(corps, c.req.raw.headers, c.env.SPAVIK_WEBHOOK_SECRET);
traiter(evenement);
return c.body(null, 200);
});Les octets, pas l'objet
La signature porte sur les octets reçus. Un corps analysé en JSON puis réécrit ne redonne pas la même signature, même s'il contient exactement les mêmes données. D'où le express.raw et le arrayBuffer ci-dessus.
La vérification refuse aussi un horodatage trop ancien, ce qui bloque le rejeu d'un appel intercepté. La tolérance est de cinq minutes par défaut.
Suspendre plutôt que supprimer
curl -X PATCH "$SPAVIK_BASE_URL/v1/webhooks/{id}" \
-H "X-API-Key: $SPAVIK_API_KEY" \
-H 'Content-Type: application/json' -d '{"active": false}'Un webhook suspendu garde son secret et sa configuration. Le supprimer vous obligerait à en recréer un, donc à changer de secret.
Le champ last_status de chaque webhook vous dit ce que votre serveur a répondu au dernier appel : de quoi repérer une intégration qui casse sans avoir à fouiller vos journaux.
Un point de cloisonnement
Les webhooks se gèrent depuis une session web uniquement. Une clé d'usage qui tente d'y accéder reçoit 403. C'est délibéré : une clé déployée dans une application ne doit pas pouvoir rediriger vos notifications.
Ne confondez pas les deux sens
Ce que décrit cette page, ce sont les webhooks sortants : Spavik appelle votre serveur. La route POST /v1/integrations/stripe/webhook est un webhook entrant, appelé par Stripe, et qui ne vous concerne pas.