webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
| Header |
Hva det er |
| webhook-id |
Leveringen, samme verdi som delivery_id i brødteksten. Husk dem du har håndtert, og svar på en gjentakelse med 200 uten å gjøre arbeidet på nytt. |
| webhook-timestamp |
Da vi signerte den, i hele sekunder siden 1970. Avvis alt som er mer enn fem minutter fra din egen klokke, så en gammel levering ikke kan sendes til deg igjen. |
| webhook-signature |
v1, deretter base64-HMAC-SHA256 av id-en, tidsstempelet og den rå brødteksten satt sammen med punktum, laget med signeringsnøkkelen din. Det kan være mer enn én, atskilt med mellomrom. Godta leveringen hvis en av dem stemmer. |
Signeringsnøkkelen
Den begynner med whsec_. Ta bort det og base64-dekod resten: de bytene er HMAC-nøkkelen, ikke teksten du kopierte. Den vises én gang, når du kobler til webhooken eller trykker på "New signing key" på Publishing-siden, og vi sender den aldri.
Kontroller den rå brødteksten
Beregn signaturen fra bytene nøyaktig slik de kom fram, før noe parser dem. JSON som er dekodet og kodet på nytt, er ikke de samme bytene, og signaturen vil ikke stemme.
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 });
});
Eller bruk et bibliotek
De offisielle Standard Webhooks-bibliotekene, for PHP, JavaScript, Python, Go, Ruby og flere, gjør alt dette i ett kall. Gi dem signeringsnøkkelen slik den ble vist til deg, inkludert whsec_.
github.com/standard-webhooks
De eldre headerne
Leveringer har også med signeringshemmeligheten som et bearer-token, og X-AutoSEO-LT-Signature, en HMAC av bare brødteksten laget med den hemmeligheten. De sendes fortsatt slik at endepunkter som bygger på dem, fortsatt virker. Et nytt endepunkt bør kontrollere webhook-signature i stedet: den gamle signaturen dekker ikke tid eller id, og nøkkelen sendes med i hver forespørsel.
Formatene AutoSEO, Outrank og BabyLoveGrowth sender nøyaktig det de tjenestene sendte, så de har ikke disse headerne.