Pular para o conteúdo

Guia

Conecte qualquer site por webhook

Não usa WordPress? Sem problema. BlogTend envia cada artigo concluído via POST como JSON assinado para um endpoint que você controla — qualquer conjunto de tecnologias, qualquer CMS, cerca de vinte linhas de código do seu lado.

O que é o conector de webhook

Quando a geração de um artigo termina — ou chega o horário agendado — BlogTend o envia ao seu endpoint em uma única requisição POST em JSON: título, HTML completo, resumo, metadados de SEO, nomes de categorias e tags, idioma, fontes, e uma URL pública para a imagem de destaque. Seu endpoint o armazena da mesma forma que seu site armazena conteúdo, e todo o restante em BlogTend funciona exatamente como nos sites WordPress: automações, agendamento, rascunhos, o painel, indexação no Google.

Cada requisição é assinada com um segredo que só você e BlogTend conhecem, para que seu endpoint possa comprovar que o artigo realmente veio de nós e não foi adulterado.

O que seu endpoint deve fazer

  • Aceite uma requisição POST via HTTPS com um corpo JSON em uma URL pública.
  • Responda em até 15 segundos com um código de status 2xx. Execute tarefas demoradas (baixar imagens, reconstruir o cache) depois de responder, não antes.
  • Responda a cada ping com um código 2xx. Um ping não contém nenhum artigo e não deve gravar nada, então você pode responder antes de verificá-lo (ou sem verificá-lo): é isso que permite que Testar & conectar funcione enquanto seu segredo ainda está sendo implantado.
  • Verifique X-BlogTend-Signature em cada evento de artigo (código abaixo), e responda com 401 quando a assinatura não corresponder. Não exigimos essa verificação, mas sem ela qualquer pessoa que encontrar a URL poderá enviar artigos falsos ao seu site.
  • Use article.id como chave de idempotência: se já existir uma publicação com esse id, atualize-a em vez de criar uma duplicata.
  • Quando você recusar uma solicitação, informe o motivo no corpo da resposta (por exemplo {"error": "assinatura inválida"}). Mostramos os primeiros 300 caracteres no seu registro de entregas.

Conectar

  1. Implante seu endpoint receptor (o exemplo no final está completo).
  2. No BlogTend, vá para Sites → Conectar um site → Webhook, insira um nome e a URL do endpoint, e pressione Avançar.
  3. Exibimos o segredo de assinatura whsec_…. Copie-o para o ambiente do seu servidor agora e faça a implantação: esta tela é o único lugar onde ele é exibido.
  4. Clique em Testar & conectar. Enviamos uma solicitação de teste assinada com esse segredo; uma resposta 2xx salva a conexão. Se o seu endpoint recusar a solicitação, mostramos o código de status e a mensagem de erro dele para que você possa corrigir o problema e clicar em Testar & conectar novamente.

Conectar a mesma URL novamente (para renomear o site, ou após um erro) nunca altera o segredo: a caixa de diálogo mostra o segredo atual e o ping de teste é assinado com ele. Para obter um novo segredo, faça a rotação dele.

Cabeçalhos enviados

Toda requisição, de ping ou de artigo, inclui estes cabeçalhos:

CabeçalhoValorNotas
X-BlogTend-Signaturet=<unix seconds>,v1=<hex>[,v1=<hex>]Assinatura HMAC-SHA256. Um v1 por segredo ativo: dois apenas durante o período de tolerância de 24 horas de uma rotação, com o mais recente primeiro.
X-BlogTend-Eventping | article.published | article.updatedIgual ao campo event no corpo.
X-BlogTend-Deliverywhd_…Igual ao delivery_id no corpo. Novo a cada tentativa, incluindo novas tentativas.
X-BlogTend-Test1Apenas em pings (Testar & conectar, Verificar novamente, atualização de categorias). Um ping não contém artigo e não deve gravar nada.
Content-Typeapplication/jsonO corpo é JSON em UTF-8.
User-AgentBlogTend-Webhook/1Era YoDon-Webhook/1 antes da mudança de nome.
X-Yodon-Signature, X-Yodon-Event, X-Yodon-DeliverydeprecatedEnviados para receptores criados antes da mudança de nome, com os mesmos valores, exceto que X-Yodon-Signature sempre contém exatamente uma assinatura v1, gerada com o segredo mais recente. Novos receptores devem ler os cabeçalhos X-BlogTend-*.

Os cabeçalhos X-Yodon-* estão obsoletos. Eles continuam funcionando para receptores criados antes da mudança de nome, mas apenas X-BlogTend-Signature contém ambas as assinaturas durante uma rotação, então migre para os novos nomes na próxima vez que você alterar seu receptor.

Os dados enviados, campo por campo

São recebidos três tipos de eventos, diferenciados pelo campo event e pelo cabeçalho X-BlogTend-Event: ping (teste de conexão, sem artigo), article.published (primeira entrega de um artigo) e article.updated (o mesmo artigo novamente: após uma nova tentativa que você acionou, a publicação de um rascunho, ou a atualização de um post que já está publicado, caso em que url indica o post publicado a ser substituído e id é o id que você armazenou para ele). A estrutura completa:

{
  "event": "article.published",
  "delivery_id": "whd_5f0c9c1e-…",
  "site_id": "d2a41c3e-…",
  "sent_at": "2026-08-28T15:04:05.000Z",
  "article": {
    "id": "a81f6a02-…",
    "title": "How to Choose a Standing Desk",
    "slug": "how-to-choose-a-standing-desk",
    "status": "publish",
    "html": "<h2>…</h2><p>…</p>",
    "excerpt": "A practical buyer's guide…",
    "seo": {
      "title": "Standing Desk Buyer's Guide (2026)",
      "description": "Everything to check before…",
      "keyword": "standing desk"
    },
    "category": "Office Setup",
    "categories": ["Office Setup", "Buying Guides"],
    "tags": ["desks", "ergonomics"],
    "language": "en",
    "featured_image": {
      "url": "https://www.blogtend.com/api/images/…?sig=…",
      "mime": "image/webp"
    },
    "sources": [{ "title": "OSHA guidance", "url": "https://…" }],
    "published_at": "2026-08-28T15:04:05.000Z",
    "updated_at": "2026-08-28T15:04:05.000Z",
    "url": "https://example.com/blog/how-to-choose-a-standing-desk"
  }
}
  • article.status é o estado que solicitamos: "publish" ou "draft". Artigos agendados chegam no horário programado com "publish".
  • article.html é o corpo completo do artigo. As referências a imagens nele já apontam para URLs públicas
  • category e categories são nomes, não IDs — associe-os à sua própria taxonomia, ou ignore-os.
  • featured_image é null quando o artigo não tem imagem. A URL é assinada e estável — acesse-a a qualquer momento.
  • delivery_id identifica esta tentativa específica e muda a cada nova tentativa; article.id identifica o artigo e nunca muda. Elimine duplicatas com base em article.id.
  • O corpo de um ping contém apenas event, delivery_id, site_id e sent_at; nenhum artigo.

Verificação da assinatura

Cada requisição contém X-BlogTend-Signature: t=<unix seconds>,v1=<hex>. Cada valor v1 é um HMAC-SHA256 da string "<t>.<raw body>" calculado com um segredo whsec_ como chave. Verifique usando o corpo bruto da requisição, antes de qualquer análise do JSON, rejeite marcas de tempo com mais de 5 minutos de diferença, compare com uma função de tempo constante, e aceite a requisição se algum v1 corresponder ao seu segredo: durante uma rotação há dois.

Node.js

import crypto from "crypto";

// header: the X-BlogTend-Signature value, e.g. "t=1767225600,v1=ab12…,v1=cd34…".
// During a secret rotation it holds one v1 per live secret: accept if ANY matches.
export function verifyBlogTendSignature(secret, header, rawBody) {
  const parts = (header || "").split(",").map((p) => p.trim());
  const t = parts.find((p) => p.startsWith("t="))?.slice(2);
  // Reject anything older than 5 minutes: replay protection.
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = Buffer.from(
    crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex")
  );
  return parts
    .filter((p) => p.startsWith("v1="))
    .map((p) => Buffer.from(p.slice(3)))
    .some((sig) => sig.length === expected.length && crypto.timingSafeEqual(sig, expected));
}

PHP

function verify_blogtend_signature(string $secret, string $header, string $rawBody): bool {
    $t = null;
    $sigs = [];
    foreach (explode(',', $header) as $part) {
        [$k, $v] = array_pad(explode('=', trim($part), 2), 2, '');
        if ($k === 't') $t = $v;
        if ($k === 'v1') $sigs[] = $v;                      // one per live secret
    }
    if (!ctype_digit((string) $t) || abs(time() - (int) $t) > 300) return false; // replay window
    $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
    foreach ($sigs as $sig) {
        if (hash_equals($expected, $sig)) return true;      // timing-safe
    }
    return false;
}

Python

import hashlib, hmac, time

def verify_blogtend_signature(secret: str, header: str, raw_body: bytes) -> bool:
    parts = [p.strip().split("=", 1) for p in (header or "").split(",") if "=" in p]
    t = next((v for k, v in parts if k == "t"), "")
    if not t.isdigit() or abs(time.time() - int(t)) > 300:   # replay window
        return False
    expected = hmac.new(
        secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256
    ).hexdigest()
    # One v1 per live secret during a rotation: accept if any matches.
    return any(hmac.compare_digest(expected, v) for k, v in parts if k == "v1")

Um receptor mais antigo que lê X-Yodon-Signature com um padrão exato de um único v1 continua funcionando: esse cabeçalho ainda contém exatamente um v1, gerado com o segredo mais recente.

Rotação do segredo

Perdeu o segredo, ou está fazendo uma substituição periódica? Na página Sites, abra o cartão do site com webhook e clique em Substituir segredo. O novo segredo é exibido uma única vez.

  • Nas próximas 24 horas cada requisição contém duas assinaturas em X-BlogTend-Signature: uma feita com o novo segredo, outra com o antigo. Um receptor que tenha qualquer um dos segredos consegue verificar a assinatura, então nada falha enquanto você atualiza seu servidor.
  • Após 24 horas, apenas o novo segredo é usado para assinar. O cartão mostra quando o antigo deixa de ser usado.
  • Se o segredo antigo vazou, marque "interromper agora" ao substituí-lo: não há período de tolerância, e as entregas falham com erro 401 até que seu servidor tenha o novo segredo.
  • Os receptores que ainda leem o cabeçalho obsoleto X-Yodon-Signature só recebem a assinatura do novo segredo, então para eles a troca é imediata.

O que responder

Qualquer resposta 2xx conta como entrega concluída; o corpo é opcional e pode estar vazio. Responda com JSON e o BlogTend fica mais inteligente:

{ "ok": true, "id": "123", "url": "https://example.com/blog/my-post" }
  • url — armazenada como o link do artigo publicado: o botão Visualizar do painel abre esse link, e a indexação do Google o envia.
  • id — o identificador da sua publicação, armazenado para que futuros eventos article.updated também possam ser associados no seu sistema.
  • held — um motivo breve quando você aceitou o artigo, mas não o publicou no seu site (uma fila editorial, uma regra interna). BlogTend então o exibe como rascunho com esse motivo em vez de uma publicação, e não envia o e-mail de "publicado". Responder com status: "draft" faz o mesmo, sem um motivo.

Para um ping, qualquer 2xx também basta. O único campo que lemos de uma resposta ao ping é categories: responda com { ok: true, categories: ["Guides", "News"] } (um array de até 100 nomes) e esses nomes aparecem como opções reais de categoria nos formulários de criação do BlogTend. Atualizado a cada nova verificação e pelo botão de atualização do seletor de categorias. Omita o campo e a IA simplesmente propõe nomes de categorias.

Falhas, novas tentativas e duplicatas

Como o BlogTend interpreta cada resposta à entrega de um artigo:

Sua respostaO que isso significa para nósReenviado?
2xxEntregue. O corpo é opcional; um corpo JSON é lido conforme descrito em O que responder.Pronto.
3xxFalha. Redirecionamentos não são seguidos: conecte a URL final em vez disso.Não é reenviado imediatamente.
401 / 403Falha: exibida como "assinatura rejeitada". A chave secreta no seu servidor não corresponde. Cole-a novamente ou gere uma nova.Não é reenviado imediatamente.
503Falha: exibida como "ainda não configurado", a resposta usual de um receptor cuja chave secreta não foi definida.Reenviado uma vez, imediatamente.
Outros 4xxFalha: você entendeu a solicitação e a recusou.Não é reenviado imediatamente.
Outros 5xxFalha: seu endpoint parou de funcionar ou está fora do ar.Reenviado uma vez, imediatamente.
Sem resposta em 15 s, ou erro de redeFalha: exibida como "não foi possível acessar seu endpoint".Reenviado uma vez, imediatamente.
  • Artigos que BlogTend publica por conta própria (automações e publicações agendadas): se uma entrega continuar falhando, o envio do artigo inteiro será tentado novamente nas próximas duas execuções, com cerca de um minuto de intervalo, independentemente da resposta recebida. Após a terceira tentativa malsucedida BlogTend interrompe as tentativas, mantém o artigo concluído e envia a você um e-mail com o motivo. Publique-o novamente assim que seu endpoint estiver corrigido.
  • Artigos que você publica manualmente: sem nova tentativa automática. O motivo aparece no artigo imediatamente; pressione Publicar novamente, ou Tentar novamente em Entregas recentes.
  • Um artigo nunca se perde: se houver uma falha, ele fica no BlogTend com o motivo, pronto para ser publicado novamente.
  • Onde consultar: na página Sites, o cartão de um site com webhook tem a seção Entregas recentes. Cada entrega mostra o código de status, o que ele geralmente significa e os primeiros 300 caracteres da sua mensagem de erro; as que falharam têm um botão Tentar novamente. O primeiro kilobyte de cada resposta é armazenado.
  • Por causa das novas tentativas, seu endpoint pode receber o mesmo artigo duas vezes, cada vez com um novo delivery_id. Por isso é importante inserir ou atualizar com base em article.id.

Um exemplo completo de receptor

Uma rota do App Router do Next.js que verifica, elimina duplicatas e armazena. Substitua savePost pela sua própria lógica de persistência e ela estará pronta para produção:

// app/api/articles/webhook/route.ts — a complete Next.js receiver
import { NextResponse } from "next/server";
import { verifyBlogTendSignature } from "./verify"; // the Node.js function above

const SECRET = process.env.BLOGTEND_WEBHOOK_SECRET; // the whsec_… shown when you connected

export async function POST(req: Request) {
  const body = await req.text(); // raw body — verify BEFORE parsing
  let payload;
  try {
    payload = JSON.parse(body);
  } catch {
    return NextResponse.json({ error: "invalid JSON" }, { status: 400 });
  }

  // A ping carries no article and writes nothing, so it is answered without
  // verifying: Test & connect then passes even before SECRET is deployed.
  // Return nothing private here.
  if (payload.event === "ping") {
    // Optional: advertise your categories so BlogTend's forms can offer them.
    return NextResponse.json({ ok: true, categories: await listMyCategories() });
  }

  // Every content event must be verified. Clear messages here show up in
  // BlogTend's delivery log, so "wrong secret" never looks like "down".
  if (!SECRET) {
    return NextResponse.json({ error: "receiver not configured: BLOGTEND_WEBHOOK_SECRET is not set" }, { status: 503 });
  }
  const header =
    req.headers.get("x-blogtend-signature") ?? req.headers.get("x-yodon-signature") ?? "";
  if (!verifyBlogTendSignature(SECRET, header, body)) {
    return NextResponse.json({ error: "invalid signature: check BLOGTEND_WEBHOOK_SECRET" }, { status: 401 });
  }

  const a = payload.article;
  // Upsert by a.id — retries and updates must not create duplicates.
  const post = await savePost({
    externalId: a.id,
    slug: a.slug,
    title: a.title,
    html: a.html,
    excerpt: a.excerpt,
    metaTitle: a.seo.title,
    metaDescription: a.seo.description,
    tags: a.tags,
    category: a.category,
    imageUrl: a.featured_image?.url ?? null,
    published: a.status === "publish",
  });

  // Answer with the live URL so the BlogTend dashboard links to the post.
  return NextResponse.json({ ok: true, id: post.id, url: post.url });
}

A mesma lógica pode ser adaptada a qualquer framework em poucos minutos — as perguntas frequentes abaixo abordam as dúvidas mais comuns sobre as plataformas.

Perguntas

Com quais plataformas isso funciona?
Qualquer conjunto de tecnologias capaz de receber um POST HTTPS: Next.js, Laravel, Django, Rails, Express, um Cloudflare Worker, uma ferramenta sem código como Make ou n8n, um CMS personalizado — se tiver uma URL, pode receber artigos.
Perdi a chave secreta de assinatura. Onde posso encontrá-la novamente?
Em nenhum lugar: nós o armazenamos criptografado e só o mostramos durante a conexão. Use Renovar segredo no cartão do site em Sites. Você recebe um novo segredo, e o antigo continua assinando junto com ele por 24 horas, para que as entregas continuem funcionando enquanto você atualiza seu servidor.
Os artigos agendados funcionam?
Sim. O agendamento fica por conta de BlogTend: no horário agendado, enviamos o artigo com o status "publish". Seu endpoint nunca precisa implementar o agendamento.
E os rascunhos?
Um artigo enviado como rascunho chega com o status "draft". Você decide o que isso significa no seu site — a maioria dos sistemas que recebem o artigo o armazena sem publicar. Quando você clicar em Publicar no BlogTend mais tarde, o mesmo artigo chegará novamente como um evento article.updated com o status "publish".
Como as imagens chegam?
A imagem de destaque é disponibilizada por uma URL pública assinada nos nossos servidores (featured_image.url), e o HTML do artigo faz referência à mesma URL. Baixe a imagem e hospede-a no seu servidor, ou disponibilize-a diretamente dos nossos — ambas as opções funcionam.
Os dados enviados podem ser reenviados? Vou receber duplicatas?
Sim. Quando uma entrega falha, ela é tentada novamente, e cada nova tentativa tem um novo delivery_id, então seu endpoint pode receber o mesmo artigo mais de uma vez. Trate article.id como a chave de idempotência: atualize o post existente em vez de criar outro, e as duplicatas se tornam impossíveis.
Os metadados de SEO são transferidos?
Sim — seo.title, seo.description e seo.keyword acompanham cada artigo, além do resumo, das etiquetas, dos nomes das categorias e do idioma. Use o que sua plataforma aceita e ignore o restante.

Vai publicar no WordPress em vez disso? Leia o guia do WordPress

Sua tecnologia, nossos artigos

Conecte um endpoint uma única vez e todos os artigos — manuais, em lote ou automatizados — vão direto para o seu site.