Signature et charges utiles
L'enveloppe de la charge utile
Chaque livraison partage la même enveloppe. L'objet data est structuré selon le type d'événement.
{
"id": "msg_3Ge4EysPsTMIbms5w4bM45ZkR5z",
"type": "review.received",
"created_at": "2026-08-30T14:12:04Z",
"api_version": "2026-06-01",
"data": {
"review_id": "rev_8kQ2mT",
"location_id": "loc_4419",
"client_id": "cli_221",
"directory": "google",
"rating": 2,
"author": "M. Ferraro",
"text": "Waited 40 minutes past my appointment.",
"language": "en",
"url": "https://maps.google.com/.../rev_8kQ2mT"
}
}api_version est fixé sur votre point de terminaison à sa création (actuellement 2026-06-01) — un futur changement non rétrocompatible de la structure de data n'affectera que les points de terminaison créés après ce changement de version.
Request headers
Chaque livraison inclut ces en-têtes en plus du corps signé :
Synup-SignatureLa signature à vérifier — voir ci-dessous.Synup-DeliveryL'id propre de l'enveloppe. Utile pour dédupliquer une livraison réessayée.Synup-EventLe type d'événement de l'enveloppe — permet de router sans analyser le corps au préalable.Vérifier les signatures
Chaque requête porte un en-tête Synup-Signature pour vous permettre de confirmer qu'elle provient bien de Synup :
Synup-Signature: t=1788112324,v1=8f4c...a91dRecalculez le HMAC-SHA256 sur t + "." + rawBody à l'aide du secret de signature de votre point de terminaison, et comparez-le à v1 en temps constant. Rejetez toute signature dont t date de plus de cinq minutes, afin de limiter l'exposition aux attaques par rejeu.
La vérification a besoin des octets bruts exacts de la requête. Si l'analyseur de corps de votre framework s'exécute d'abord et resérialise le JSON — même en reformatant simplement les espaces —, la signature ne correspondra plus. Lisez le corps brut de cette route avant qu'un middleware d'analyse JSON n'y touche.
Exemple
const crypto = require("crypto");
function verifyWebhookSignature(header, rawBody, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const t = Number(parts.t);
const v1 = parts.v1;
if (!t || !v1) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const signedPayload = `${t}.${rawBody}`;
const expected = crypto.createHmac("sha256", secret).update(signedPayload).digest("hex");
const a = Buffer.from(v1, "hex");
const b = Buffer.from(expected, "hex");
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
// Express example
app.post("/synup/webhooks", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.header("Synup-Signature");
if (!verifyWebhookSignature(signature, req.body.toString("utf8"), process.env.SYNUP_WEBHOOK_SECRET)) {
return res.status(401).send("invalid signature");
}
const event = JSON.parse(req.body);
// ... handle event.type / event.data
res.status(200).send({ received: true });
});Testez votre implémentation
Exécutez votre vérificateur avec ces valeurs fixes — si votre résultat correspond à la signature attendue ci-dessous, votre implémentation est correcte.
whsec_test_secret_keyHorodatage1700000000Charge utile{"id":"evt_test123","type":"review.received","created_at":"2023-11-14T22:13:20Z","api_version":"2026-06-01","data":{"review_id":"rev_test","rating":5}}Signature attenduet=1700000000,v1=6204bc359e5eabdf2060a37634581c0276a9c99221d54a6063cb4373f43b2b04Bac à sable de signature
Calcule le HMAC entièrement dans votre navigateur — rien n'est envoyé où que ce soit. Collez votre propre secret et votre charge utile pour voir l'en-tête exact que nous enverrions.