webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
| Zaglavlje |
Što je to |
| webhook-id |
Isporuka, ista vrijednost kao delivery_id u tijelu. Zapamtite one koje ste obradili i na ponavljanje odgovorite s 200 bez ponovnog obavljanja posla. |
| webhook-timestamp |
Vrijeme kada smo to potpisali, u cijelim sekundama od 1970. Odbijte sve što odstupa više od pet minuta od Vašeg sata, kako Vam se stara isporuka ne bi mogla ponovno poslati. |
| webhook-signature |
v1, zatim base64 HMAC-SHA256 od id-a, vremenske oznake i neobrađenog tijela, spojenih točkama, izrađen Vašim ključem za potpisivanje. Može ih biti više, odvojenih razmacima. Prihvatite isporuku ako se bilo koji od njih podudara. |
Ključ za potpisivanje
Počinje s whsec_. Uklonite to i ostatak dekodirajte iz base64: ti bajtovi su HMAC ključ, a ne tekst koji ste kopirali. Prikazuje se jednom, kada povežete webhook ili pritisnete "New signing key" na stranici Publishing, i nikada ga ne šaljemo.
Provjerite neobrađeno tijelo
Izračunajte potpis iz bajtova točno onako kako su stigli, prije nego što ih bilo što obradi. JSON koji je dekodiran pa ponovno kodiran nisu isti bajtovi i njegov se potpis neće podudarati.
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 });
});
Ili upotrijebite biblioteku
Službene biblioteke Standard Webhooks za PHP, JavaScript, Python, Go, Ruby i druge sve to rade jednim pozivom. Dajte im ključ za potpisivanje točno kako Vam je prikazan, uključujući whsec_.
github.com/standard-webhooks
Starija zaglavlja
Isporuke nose i tajnu za potpisivanje kao bearer token te X-AutoSEO-LT-Signature, HMAC samo tijela izrađen tom tajnom. Oni se i dalje šalju kako bi krajnje točke koje se na njima temelje nastavile raditi. Nova krajnja točka umjesto toga treba provjeravati webhook-signature: stari potpis ne obuhvaća vrijeme ni id, a njegov ključ putuje u svakom zahtjevu.
Formati AutoSEO, Outrank i BabyLoveGrowth šalju točno ono što su te usluge poslale, pa ne nose ta zaglavlja.