webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
| Header |
Che cos'è |
| webhook-id |
La consegna, lo stesso valore di delivery_id nel corpo. Tenga memoria di quelle già gestite e risponda a una ripetizione con 200 senza rifare il lavoro. |
| webhook-timestamp |
Quando l'abbiamo firmata, in secondi interi dal 1970. Rifiuti tutto ciò che differisce di più di cinque minuti dal Suo orologio, così una vecchia consegna non può esserLe inviata di nuovo. |
| webhook-signature |
v1, poi l'HMAC-SHA256 in base64 dell'id, del timestamp e del corpo grezzo uniti da punti, creato con la Sua chiave di firma. Può essercene più di uno, separati da spazi. Accetti la consegna se uno qualsiasi corrisponde. |
La chiave di firma
Inizia con whsec_. Tolga quel prefisso e decodifichi il resto da base64: quei byte sono la chiave HMAC, non il testo che ha copiato. Viene mostrata una sola volta, quando collega il webhook o preme "New signing key" nella pagina Publishing, e noi non la inviamo mai.
Controlli il corpo grezzo
Calcoli la firma dai byte esattamente come sono arrivati, prima che qualcosa li analizzi. Un JSON che è stato decodificato e codificato di nuovo non ha gli stessi byte e la sua firma non corrisponderà.
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 });
});
Oppure usi una libreria
Le librerie ufficiali Standard Webhooks, per PHP, JavaScript, Python, Go, Ruby e altro, fanno tutto questo con una sola chiamata. Fornisca loro la chiave di firma esattamente come Le è stata mostrata, whsec_ incluso.
github.com/standard-webhooks
Gli header precedenti
Le consegne includono anche il segreto di firma come bearer token e X-AutoSEO-LT-Signature, un HMAC del solo corpo creato con quel segreto. Vengono ancora inviati perché gli endpoint che si basano su di essi continuino a funzionare. Un nuovo endpoint dovrebbe invece controllare webhook-signature: la vecchia firma non copre né l'ora né l'id, e la sua chiave viaggia in ogni richiesta.
I formati AutoSEO, Outrank e BabyLoveGrowth inviano esattamente ciò che quei servizi hanno inviato, quindi non includono questi header.