Référence API Webhooks
Format de la charge utile, en-têtes, vérification de signature, tentatives et liste complète des événements.
Requête
Chaque livraison est un POST avec un corps JSON et ces en-têtes : Serpfull-Signature, Serpfull-Timestamp, Serpfull-Event-Id, Serpfull-Event-Type, Serpfull-Delivery-Attempt (1–8), Serpfull-Api-Version (2026-10-26) et User-Agent Serpfull-Webhooks/2.0.
Charge utile
Champs : id (identifiant de l’événement, identique lors des tentatives et renvois), type, api_version, created_at (ISO 8601, UTC), created (secondes Unix), org_id, site (id, domaine et nom du site web, ou null pour les événements d’organisation) et data (spécifique à l’événement).
{
"id": "6f1c2b8e-3a4d-4e5f-9a1b-2c3d4e5f6a7b",
"type": "uptime.monitor.down",
"api_version": "2026-10-26",
"created_at": "2026-10-26T09:14:03.120Z",
"created": 1793006043,
"org_id": "0d9a…",
"site": { "id": "8b2e…", "domain": "example.co.uk", "name": "Example" },
"data": { "monitor_id": "…", "url": "https://example.co.uk/", "reason": "timeout" }
}Signature
Serpfull-Signature est t=TIMESTAMP,v1=SIGNATURE. SIGNATURE est le HMAC-SHA256 hexadécimal de la chaîne TIMESTAMP + "." + corps brut de la requête, signé avec le secret de signature de votre point de terminaison. Lors d'une rotation de secret, il y a deux valeurs v1 ; acceptez la requête si l'une ou l'autre correspond. Comparez en temps constant et rejetez les horodatages à plus de 5 minutes de votre horloge pour bloquer les rejouements.
Vérifier en Node.js
import crypto from "node:crypto"
// Use the raw request body (a string or Buffer), not re-serialised JSON.
export function verifySerpfull(rawBody, header, secret, toleranceSeconds = 300) {
const parts = header.split(",").map((p) => p.trim().split("="))
const t = Number(parts.find(([k]) => k === "t")?.[1])
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest()
return parts.some(([k, v]) => k === "v1" && v?.length === expected.length * 2 &&
crypto.timingSafeEqual(Buffer.from(v, "hex"), expected))
}
// Express example
app.post("/webhooks/serpfull", express.raw({ type: "application/json" }), (req, res) => {
if (!verifySerpfull(req.body.toString("utf8"), req.get("serpfull-signature") ?? "", process.env.SERPFULL_WEBHOOK_SECRET)) {
return res.status(400).end()
}
const event = JSON.parse(req.body)
// Deduplicate on event.id (also sent as the Serpfull-Event-Id header), then handle event.type.
res.status(200).end()
})Vérifier en Python
import hashlib, hmac, time
def verify_serpfull(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = [p.strip().split("=", 1) for p in header.split(",") if "=" in p]
try:
t = int(next(v for k, v in parts if k == "t"))
except (StopIteration, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return any(k == "v1" and hmac.compare_digest(v, expected) for k, v in parts)
# Flask example
@app.post("/webhooks/serpfull")
def serpfull_webhook():
if not verify_serpfull(request.get_data(), request.headers.get("Serpfull-Signature", ""), os.environ["SERPFULL_WEBHOOK_SECRET"]):
abort(400)
event = request.get_json()
# Deduplicate on event["id"], then handle event["type"].
return "", 200Réponses et nouvelles tentatives
- 2xx dans les 10 secondes : livré.
- Tout autre cas, un délai d’attente ou une erreur de connexion : nouvelle tentative après 1 m, 5 m, 30 m, 2 h, 6 h, 12 h et 24 h (8 tentatives au total).
- 410 Gone : nous arrêtons de retenter cet événement.
- Les livraisons sont au moins une fois et peuvent arriver dans le désordre ; dédupliquez sur l’ID de l’événement et utilisez created_at pour ordonner.
Événements
- serp.run.completed : Une recherche s’est terminée et a cliqué sur votre annonce.
- serp.run.not_found : Une recherche n’a pas trouvé votre site dans les résultats.
- serp.run.site_challenge : Votre site a affiché une page de défi lors d’une recherche.
- serp.run.failed : Une recherche a échoué (délai, blocage ou autre erreur).
- serp.term.auto_paused : Un terme a été mis en pause après 3 recherches consécutives sans trouver votre site.
- serp.failure_rate.high : Plus de la moitié des recherches d’un site ont échoué en 24 heures.
- local.run.completed : Une recherche Local Business s’est terminée et a ouvert votre annonce.
- local.run.not_found : Une recherche Local Business n’a pas trouvé votre annonce.
- local.run.site_challenge : Votre site a affiché une page de défi lors d’une recherche Local Business.
- local.run.failed : Une recherche Local Business a échoué.
- local.rank.changed : Une position locale suivie a changé.
- aio.run.completed : Une recherche AI Overview s’est terminée et a cliqué sur votre citation.
- aio.run.not_found : Une recherche AI Overview n’a pas trouvé votre site cité.
- aio.run.site_challenge : Votre site a affiché une page de défi lors d’une recherche AI Overview.
- aio.run.failed : Une recherche AI Overview a échoué.
- aio.citation.gained : Votre site a commencé à être cité dans AI Overview pour un terme suivi.
- aio.citation.lost : Votre site a cessé d’être cité dans AI Overview pour un terme suivi.
- serp.rank.changed : Une position suivie a changé.
- competitor.rank.changed : Un concurrent suivi a bougé de 3+ places ou est entré ou sorti du top 10 ou 30.
- rank_ai.summary_ready : Un rapport Rank AI est terminé, avec le score de santé, le résumé et les principales recommandations.
- rank_ai.review_ready : Une revue de page Rank AI est terminée, avec le score de contenu, les modifications suggérées et les sujets manquants.
- website.verified : La propriété d’un site a été vérifiée.
- uptime.monitor.down : Un moniteur de disponibilité a confirmé une panne.
- uptime.monitor.up : Un moniteur de disponibilité est revenu.
- domains.expiry.warning : Un enregistrement de domaine ou un certificat SSL est proche de l’expiration.
- domains.change.detected : Un domaine surveillé a changé (DNS, registraire, certificat et plus).
- ssl.certificate.expiring : Un certificat expire dans votre fenêtre d’alerte.
- audit.completed : Un audit SEO est terminé.
- audit.score_dropped : Le score SEO d’une page a chuté de 5 points ou plus.
- audit.issue.new : Un audit a trouvé des problèmes qui n’étaient pas présents la dernière fois.
- analytics.traffic.spike : Les visites ont fortement augmenté par rapport au niveau habituel.
- analytics.traffic.drop : Les visites ont fortement chuté par rapport au niveau habituel.
- backlinks.domain.new : De nouveaux domaines référents ont été trouvés.
- backlinks.domain.lost : Des domaines référents ont été perdus.
- bot.attack.detected : Le trafic des bots malveillants a fortement augmenté au-dessus de la normale.
- cookies.scan.completed : Un scan des cookies est terminé.
- report.generated : Un rapport a été généré.
- exchange.installed : Votre bloc Partenaires a été trouvé et le site web a rejoint.
- exchange.link_missing : Votre bloc Partenaires a disparu, vos liens sont donc en pause.
- exchange.partner_added : Un partenaire a été ajouté à votre bloc, ou vous au sien.
- billing.subscription.updated : Un abonnement a été créé, modifié ou annulé.
- plan.limit.reached : Un site web a utilisé toute l’allocation d’un plan.
- billing.payment.failed : Un paiement pour votre compte n’a pas abouti.
- workflow.run.succeeded : Une exécution de workflow en direct s’est terminée avec succès.
- workflow.run.failed : Une exécution de workflow en direct a échoué.
- workflow.approval.requested : Un workflow attend qu’une étape soit approuvée.
- workflow.custom : Un workflow a envoyé un événement personnalisé avec sa propre charge utile.
Exécuter les événements
serp.run.*, local.run.* et aio.run.* contiennent run_id, website_id, search_term_id, module, outcome, position, page, dwell_seconds, pages_visited, device, country, started_at, finished_at et error_code. completed signifie que la fiche a été trouvée et cliquée, not_found qu’elle n’était pas dans les résultats, site_challenge que votre site a affiché une page de défi, et failed couvre les délais d’attente, blocages et autres erreurs.
Événements de test
test.ping est envoyé uniquement lorsque vous appuyez sur Envoyer un événement test (ou utilisez webhooks_send_test). Ses données contiennent un message et votre organisation.
Mis à jour