webhook-id: 7c1e2f0a-3b5d-4e8f-9a61-2d4c8b0e5f13
webhook-timestamp: 1790490600
webhook-signature: v1,s0RzLJ1d7Cxd9nwe8pqER9DWy/stEIB+uT9MlGVLlM0=
| Header |
Τι είναι |
| webhook-id |
Η παράδοση, η ίδια τιμή με το delivery_id στο σώμα. Να θυμάστε ποιες έχετε ήδη χειριστεί και να απαντάτε σε επανάληψη με 200 χωρίς να κάνετε ξανά την ίδια εργασία. |
| webhook-timestamp |
Πότε το υπογράψαμε, σε ολόκληρα δευτερόλεπτα από το 1970. Απορρίψτε οτιδήποτε απέχει περισσότερο από πέντε λεπτά από το δικό σας ρολόι, ώστε να μη σας σταλεί ξανά μια παλιά παράδοση. |
| webhook-signature |
v1 και μετά το base64 HMAC-SHA256 του id, της χρονικής σήμανσης και του ακατέργαστου σώματος, ενωμένων με τελείες, που δημιουργείται με το κλειδί υπογραφής σας. Μπορεί να υπάρχουν περισσότερα από ένα, χωρισμένα με κενά. Αποδεχτείτε την παράδοση αν ταιριάζει οποιοδήποτε από αυτά. |
Το κλειδί υπογραφής
Ξεκινά με whsec_. Αφαιρέστε το και κάντε αποκωδικοποίηση base64 στο υπόλοιπο: αυτά τα bytes είναι το κλειδί HMAC, όχι το κείμενο που αντιγράψατε. Εμφανίζεται μία φορά, όταν συνδέετε το webhook ή πατάτε "New signing key" στη σελίδα Publishing, και δεν το στέλνουμε ποτέ.
Ελέγξτε το ακατέργαστο σώμα
Υπολογίστε την υπογραφή από τα bytes ακριβώς όπως έφτασαν, πριν τα επεξεργαστεί οτιδήποτε. JSON που έχει αποκωδικοποιηθεί και κωδικοποιηθεί ξανά δεν είναι τα ίδια bytes και η υπογραφή του δεν θα ταιριάζει.
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 });
});
Ή χρησιμοποιήστε μια βιβλιοθήκη
Οι επίσημες βιβλιοθήκες Standard Webhooks, για PHP, JavaScript, Python, Go, Ruby και άλλα, τα κάνουν όλα αυτά με μία κλήση. Δώστε τους το κλειδί υπογραφής όπως σας εμφανίστηκε, μαζί με το whsec_.
github.com/standard-webhooks
Τα παλαιότερα headers
Οι παραδόσεις περιλαμβάνουν επίσης το μυστικό υπογραφής ως bearer token και το X-AutoSEO-LT-Signature, ένα HMAC μόνο του σώματος που δημιουργείται με αυτό το μυστικό. Εξακολουθούν να αποστέλλονται ώστε τα endpoints που βασίζονται σε αυτά να συνεχίσουν να λειτουργούν. Ένα νέο endpoint θα πρέπει να ελέγχει αντί γι' αυτό το webhook-signature: η παλιά υπογραφή δεν καλύπτει χρόνο ούτε id και το κλειδί της μεταφέρεται σε κάθε αίτημα.
Οι μορφές AutoSEO, Outrank και BabyLoveGrowth στέλνουν ακριβώς ό,τι έστειλαν αυτές οι υπηρεσίες, οπότε δεν περιλαμβάνουν αυτά τα headers.