v1Übersicht/Signatur & Nutzlast

Signatur & Nutzlast

Der Nutzlast-Umschlag

Jede Zustellung verwendet denselben Umschlag. Das data-Objekt richtet sich nach dem Ereignistyp.

{
  "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 wird bei der Erstellung Ihres Endpunkts festgelegt (derzeit 2026-06-01) — eine spätere, nicht abwärtskompatible Änderung an der data-Struktur betrifft nur Endpunkte, die nach dieser Versionsänderung erstellt wurden.

Request headers

Jede Zustellung enthält diese Header zusätzlich zum signierten Body:

Synup-SignatureDie zu verifizierende Signatur — siehe unten.Synup-DeliveryDie eigene id der Nutzlast. Nützlich, um eine wiederholte Zustellung zu deduplizieren.Synup-EventDer Ereignistyp der Nutzlast — ermöglicht Routing, ohne den Body zu parsen.

Signaturen überprüfen

Jede Anfrage enthält einen Synup-Signature-Header, mit dem Sie prüfen können, dass sie wirklich von Synup stammt:

Synup-Signature: t=1788112324,v1=8f4c...a91d

Berechnen Sie HMAC-SHA256 über t + "." + rawBody mit dem Signaturschlüssel Ihres Endpunkts neu und vergleichen Sie das Ergebnis in konstanter Zeit mit v1. Verwerfen Sie alles, bei dem t älter als fünf Minuten ist, um das Risiko von Replay-Angriffen zu begrenzen.

Analysieren Sie den Body nicht vor der Überprüfung

Die Überprüfung benötigt genau die rohen Anfrage-Bytes. Wenn der Body-Parser Ihres Frameworks zuerst läuft und JSON neu serialisiert — selbst nur durch Neuformatierung der Leerzeichen —, stimmt die Signatur nicht mehr überein. Lesen Sie den rohen Body für diese Route, bevor eine JSON-Parsing-Middleware ihn berührt.

Beispiel

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 });
});

Testen Sie Ihre Implementierung

Führen Sie Ihre Überprüfung mit diesen festen Werten aus — wenn Ihre Ausgabe mit der unten erwarteten Signatur übereinstimmt, ist Ihre Implementierung korrekt.

Secretwhsec_test_secret_keyZeitstempel1700000000Nutzlast{"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}}Erwartete Signaturt=1700000000,v1=6204bc359e5eabdf2060a37634581c0276a9c99221d54a6063cb4373f43b2b04

Signatur-Spielwiese

Signatur-Spielwiese

Berechnet den HMAC vollständig in Ihrem Browser — hier wird nichts irgendwohin gesendet. Fügen Sie Ihr eigenes Secret und Ihre Nutzlast ein, um den genauen Header zu sehen, den wir senden würden.