webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
| Header |
Wat het is |
| webhook-id |
De levering, dezelfde waarde als delivery_id in de body. Onthoud welke u al hebt verwerkt, en beantwoord een herhaling met 200 zonder het werk opnieuw te doen. |
| webhook-timestamp |
Toen wij het ondertekenden, in hele seconden sinds 1970. Weiger alles wat meer dan vijf minuten afwijkt van uw eigen klok, zodat een oude levering niet opnieuw naar u kan worden gestuurd. |
| webhook-signature |
v1, gevolgd door de base64-HMAC-SHA256 van de id, de tijdstempel en de onbewerkte body, samengevoegd met punten, gemaakt met uw ondertekeningssleutel. Er kunnen er meer dan één zijn, gescheiden door spaties. Accepteer de levering als een ervan overeenkomt. |
De ondertekeningssleutel
Het begint met whsec_. Haal dat weg en decodeer de rest met base64: die bytes zijn de HMAC-sleutel, niet de tekst die u hebt gekopieerd. Die wordt één keer getoond, wanneer u de webhook koppelt of op de pagina Publishing op "New signing key" drukt, en we versturen die nooit.
Controleer de onbewerkte body
Bereken de handtekening op basis van de bytes precies zoals ze zijn aangekomen, voordat iets ze parseert. JSON dat is gedecodeerd en opnieuw gecodeerd, bestaat niet uit dezelfde bytes, en de handtekening komt dan niet overeen.
PHP
<?php
$raw = getenv('SEONIS_SIGNING_KEY'); // whsec_...
// No key, no deliveries. A missing or empty variable would otherwise become
// an empty HMAC key, and anybody can sign with that.
$secret = is_string($raw) && str_starts_with($raw, 'whsec_')
? base64_decode(substr($raw, strlen('whsec_')), true)
: false;
if ($secret === false || strlen($secret) < 24) {
http_response_code(500);
exit('Signing key not configured.');
}
$id = $_SERVER['HTTP_WEBHOOK_ID'] ?? '';
$timestamp = $_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '';
$header = $_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
if (! ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401);
exit('Too old.');
}
$expected = base64_encode(hash_hmac('sha256', "$id.$timestamp.$body", $secret, true));
$valid = false;
foreach (explode(' ', $header) as $signature) {
[$version, $value] = array_pad(explode(',', $signature, 2), 2, '');
$valid = $valid || ($version === 'v1' && hash_equals($expected, $value));
}
if (! $valid) {
http_response_code(401);
exit('Bad signature.');
}
$delivery = json_decode($body, true);
Node.js
import crypto from 'node:crypto';
import express from 'express';
const app = express();
// No key, no server. An empty variable would otherwise become an empty HMAC
// key, and anybody can sign with that.
const raw = process.env.SEONIS_SIGNING_KEY ?? '';
if (!raw.startsWith('whsec_')) throw new Error('SEONIS_SIGNING_KEY is not set.');
const key = Buffer.from(raw.slice('whsec_'.length), 'base64');
if (key.length < 24) throw new Error('SEONIS_SIGNING_KEY is not a valid key.');
// express.raw keeps the body as the bytes we signed.
app.post('/hooks/seonis', express.raw({ type: 'application/json' }), (req, res) => {
const id = req.get('webhook-id') ?? '';
const timestamp = req.get('webhook-timestamp') ?? '';
const header = req.get('webhook-signature') ?? '';
if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(401).send('Too old.');
}
const expected = crypto.createHmac('sha256', key)
.update(`${id}.${timestamp}.`)
.update(req.body)
.digest();
const valid = header.split(' ').some((signature) => {
const [version, value = ''] = signature.split(',');
const given = Buffer.from(value, 'base64');
return version === 'v1' && given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
if (!valid) return res.status(401).send('Bad signature.');
const delivery = JSON.parse(req.body);
res.json({ ok: true });
});
Of gebruik een bibliotheek
De officiële Standard Webhooks-bibliotheken, voor PHP, JavaScript, Python, Go, Ruby en meer, doen dit alles in één aanroep. Geef ze de ondertekeningssleutel zoals die aan u is getoond, inclusief whsec_.
github.com/standard-webhooks
De oudere headers
Leveringen bevatten ook het ondertekeningsgeheim als bearer token, en X-AutoSEO-LT-Signature, een HMAC van alleen de body gemaakt met dat geheim. Die worden nog steeds meegestuurd zodat endpoints die daarop zijn gebouwd blijven werken. Een nieuw endpoint moet in plaats daarvan webhook-signature controleren: de oude handtekening dekt geen tijd en geen id, en de sleutel ervan gaat mee in elk verzoek.
De indelingen AutoSEO, Outrank en BabyLoveGrowth sturen precies wat die diensten hebben gestuurd, dus ze bevatten deze headers niet.