Referência da API de webhooks
Formato do payload, cabeçalhos, verificação da assinatura, novas tentativas e a lista completa de eventos.
Solicitação
Cada entrega é um POST com corpo JSON e estes cabeçalhos: Serpfull-Signature, Serpfull-Timestamp, Serpfull-Event-Id, Serpfull-Event-Type, Serpfull-Delivery-Attempt (1–8), Serpfull-Api-Version (2026-10-26) e User-Agent Serpfull-Webhooks/2.0.
Payload
Campos: id (id do evento, o mesmo em novas tentativas e reenvios), type, api_version, created_at (ISO 8601, UTC), created (segundos Unix), org_id, site (id, domínio e nome do site, ou null em eventos da organização) e data (específico de cada evento).
{
"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" }
}Assinatura
Serpfull-Signature é t=TIMESTAMP,v1=SIGNATURE. SIGNATURE é o HMAC-SHA256 em hexadecimal da string TIMESTAMP + "." + o corpo bruto da solicitação, usando como chave o segredo de assinatura do seu endpoint. Durante uma rotação do segredo há dois valores v1; aceite a solicitação se qualquer um corresponder. Compare em tempo constante e rejeite timestamps com mais de 5 minutos de diferença do seu relógio para bloquear repetições.
Verificar em 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()
})Verificar em 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 "", 200Respostas e novas tentativas
- 2xx em até 10 segundos: entregue.
- Qualquer outra resposta, tempo esgotado ou erro de conexão: nova tentativa após 1 m, 5 m, 30 m, 2 h, 6 h, 12 h e 24 h (8 tentativas no total).
- 410 Gone: paramos de tentar esse evento.
- As entregas são pelo menos uma vez e podem chegar fora de ordem; elimine duplicados pelo id do evento e use created_at para ordenar.
Eventos
- serp.run.completed: Uma execução de busca terminou e clicou na sua listagem.
- serp.run.not_found: Uma execução de busca não encontrou seu site nos resultados.
- serp.run.site_challenge: Seu site mostrou uma página de verificação durante uma execução.
- serp.run.failed: Uma execução de busca falhou (tempo esgotado, bloqueio ou outro erro).
- serp.term.auto_paused: Um termo foi pausado depois de 3 execuções seguidas sem encontrar seu site.
- serp.failure_rate.high: Mais da metade das execuções de um site falhou em 24 horas.
- local.run.completed: Uma execução de Negócio local terminou e abriu sua listagem.
- local.run.not_found: Uma execução de Negócio local não encontrou sua listagem.
- local.run.site_challenge: Seu site mostrou uma página de verificação durante uma execução de Negócio local.
- local.run.failed: Uma execução de Negócio local falhou.
- aio.run.completed: Uma execução de AI Overview terminou e clicou na sua citação.
- aio.run.not_found: Uma execução de AI Overview não encontrou seu site citado.
- aio.run.site_challenge: Seu site mostrou uma página de verificação durante uma execução de AI Overview.
- aio.run.failed: Uma execução de AI Overview falhou.
- serp.rank.changed: Uma posição acompanhada mudou.
- competitor.rank.changed: Um concorrente acompanhado subiu ou caiu 3 ou mais posições, ou entrou ou saiu do top 10 ou do top 30.
- rank_ai.summary_ready: Um relatório do Rank AI terminou, com a pontuação de saúde, o resumo e as principais recomendações.
- rank_ai.review_ready: Terminou uma análise de página do Rank AI, com a pontuação de conteúdo, as alterações sugeridas e os temas em falta.
- website.verified: A propriedade de um site foi verificada.
- uptime.monitor.down: Um monitor de disponibilidade confirmou uma queda.
- uptime.monitor.up: Um monitor de disponibilidade se recuperou.
- domains.expiry.warning: O registro de um domínio ou um certificado SSL está perto de vencer.
- domains.change.detected: Um domínio monitorado mudou (DNS, registrador, certificado e mais).
- audit.completed: Uma auditoria de SEO terminou.
- audit.score_dropped: A pontuação de SEO de uma página caiu 5 pontos ou mais.
- backlinks.domain.new: Foram encontrados novos domínios de referência.
- backlinks.domain.lost: Domínios de referência foram perdidos.
- bot.attack.detected: O tráfego de bots maliciosos subiu muito acima do normal.
- cookies.scan.completed: Uma verificação de cookies terminou.
- report.generated: Um relatório foi gerado.
- exchange.installed: Seu bloco de Partners foi encontrado e o site entrou.
- exchange.link_missing: Seu bloco de Partners sumiu, então seus links estão pausados.
- exchange.partner_added: Um parceiro foi adicionado ao seu bloco, ou você ao dele.
- billing.subscription.updated: Uma assinatura foi criada, alterada ou cancelada.
- plan.limit.reached: Um site usou toda uma cota do plano.
Eventos de execução
serp.run.*, local.run.* e aio.run.* trazem run_id, website_id, search_term_id, module, outcome, position, page, dwell_seconds, pages_visited, device, country, started_at, finished_at e error_code. completed significa que a listagem foi encontrada e clicada, not_found que ela não estava nos resultados, site_challenge que seu site mostrou uma página de verificação, e failed cobre tempos esgotados, bloqueios e outros erros.
Eventos de teste
test.ping só é enviado quando você clica em Enviar evento de teste (ou usa webhooks_send_test). O data dele traz uma mensagem e a sua organização.
Updated