webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
| Otsake |
Mikä se on |
| webhook-id |
Toimitus, sama arvo kuin rungon delivery_id. Pitäkää kirjaa käsittelemistänne toimituksista ja vastatkaa toistoon koodilla 200 tekemättä työtä uudelleen. |
| webhook-timestamp |
Hetki, jolloin allekirjoitimme sen, kokonaisina sekunteina vuodesta 1970. Hylätkää kaikki, mikä poikkeaa omasta kellostanne yli viisi minuuttia, jotta vanhaa toimitusta ei voi lähettää teille uudelleen. |
| webhook-signature |
v1, sitten tunnisteesta, aikaleimasta ja raakadatasta pisteillä yhdistetty base64-HMAC-SHA256, tehtynä allekirjoitusavaimellanne. Niitä voi olla useampi kuin yksi, välilyönneillä erotettuna. Hyväksykää toimitus, jos mikä tahansa niistä täsmää. |
Allekirjoitusavain
Se alkaa merkkijonolla whsec_. Poistakaa se alusta ja base64-purkakaa loppuosa: nuo tavut ovat HMAC-avain, eivät kopioimanne teksti. Se näytetään kerran, kun yhdistätte webhookin tai painatte Publishing-sivulla "New signing key", emmekä koskaan lähetä sitä.
Tarkistakaa raaka runko
Laskekaa allekirjoitus tavuista täsmälleen siinä muodossa kuin ne saapuivat, ennen kuin mikään jäsentää niitä. Purettu ja uudelleen koodattu JSON ei ole samoja tavuja, eikä sen allekirjoitus täsmää.
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 });
});
Tai käyttäkää kirjastoa
Viralliset Standard Webhooks -kirjastot PHP:lle, JavaScriptille, Pythonille, Golle, Rubylle ja muille hoitavat kaiken tämän yhdellä kutsulla. Antakaa niille allekirjoitusavain täsmälleen siinä muodossa kuin se näytettiin teille, whsec_ mukana.
github.com/standard-webhooks
Vanhemmat otsakkeet
Toimituksissa on myös allekirjoitussalaisuus bearer-tokenina sekä X-AutoSEO-LT-Signature, joka on pelkästä rungosta tällä salaisuudella tehty HMAC. Ne lähetetään yhä, jotta niiden varaan rakennetut päätepisteet toimivat edelleen. Uuden päätepisteen kannattaa tarkistaa sen sijaan webhook-signature: vanha allekirjoitus ei kata aikaa eikä tunnistetta, ja sen avain kulkee jokaisessa pyynnössä.
AutoSEO-, Outrank- ja BabyLoveGrowth-muodot lähettävät täsmälleen sen, mitä kyseiset palvelut lähettivät, joten niissä ei ole näitä otsakkeita.