Webhooks do Flux
Em vez de perguntar à API de tempo em tempo se algo mudou, cadastre um endereço e o Flux avisa no instante em que muda: um post criado, agendado, publicado ou com falha, uma decisão de aprovação, uma conta conectada que caiu ou voltou.
O modelo é o dos webhooks do Stripe: você escolhe os eventos e os projetos, cada aviso chega num envelope padronizado e assinado, e o que falha é reenviado com espaçamento crescente.
9 eventos
Ciclo do post, publicação, aprovação e contas conectadas. Cada webhook recebe só os que você marcou.
Por projeto
Todos os projetos, inclusive os próximos, ou só os escolhidos, dentro do que você alcança no Flux.
Assinado
Todo aviso leva o cabeçalho Flux-Signature, um HMAC-SHA256 do corpo com o segredo do webhook.
O aviso diz que mudou; a API é a fonte da verdade
O objeto que chega no aviso tem o mesmo formato de GET /api/v1/posts/{id} e GET /api/v1/integrations. Na dúvida sobre ordem ou estado, consulte o recurso na API.
Cadastrar um webhook
O cadastro é feito na tela da conta, e não pela API. Já autenticado no Flux, acesse Empresa → Webhooks, em /company/edit/webhooks
- 1Clique em Criar webhook.
- 2Informe o endereço (URL https) que vai receber os avisos e, se quiser, uma descrição.
- 3Marque os eventos que esse endereço deve receber (ao menos um).
- 4Escolha os projetos: todos os atuais e os próximos, ou uma seleção.
- 5Salve e copie o segredo (whsec_…). É com ele que você confere a assinatura de cada aviso.
Regras do cadastro
Cada conta tem até 5 webhooks. Quem pode cadastrar é quem pode gerenciar as chaves de acesso da conta.
O endereço precisa ser https e público: endereço interno, http ou domínio que não existe são recusados no cadastro.
O webhook só recebe avisos de projetos que você alcança no Flux. Quem alcança todos vê a opção "Todos os projetos atuais e os próximos"; quem alcança só alguns vê "Todos os projetos que eu acesso", que vale para esses.
Depois de cadastrado
Enviar teste
Envia na hora um post.updated do post alterado mais recentemente nos projetos do webhook, com livemode false, e mostra a resposta do seu endereço: status HTTP, erro e tempo.
Histórico de entregas
Cada tentativa fica registrada com o status (Entregue, Aguardando, Falhou, Desistiu), o código HTTP, o começo da resposta e a próxima tentativa. O histórico guarda os últimos 30 dias.
Reenviar
Uma entrega que esgotou as tentativas (Desistiu) pode ser reenviada pelo histórico, e isso abre um ciclo novo de tentativas.
Trocar segredo
Gera um segredo novo. O atual continua assinando junto por 24 horas, para dar tempo de atualizar o seu sistema sem perder aviso. O segredo pode ser consultado na tela sempre que precisar.
Reativar
Um webhook desativado, seja por falhas, pelo seu endereço (410) ou à mão, volta a receber avisos a partir da reativação.
Eventos
Os eventos estão agrupados como na tela de cadastro. O objeto é o que vem em data.object: o post ou a conta conectada.
| Evento | Quando | Objeto |
|---|---|---|
| Ciclo do post | ||
post.createdPost criado | Um post foi criado, pela tela, pela API ou pelo MCP | post |
post.updatedPost alterado | Mudou a legenda, a data, o tipo, a conta ou o link publicado, sem mudar o status | post |
post.deletedPost excluído | O post foi excluído | post |
| Publicação | ||
post.scheduledPost agendado | O post saiu de rascunho ou de aprovação e foi agendado | post |
post.publishedPost publicado | O post foi publicado na rede | post |
post.failedFalha na publicação | A publicação falhou | post |
| Aprovação | ||
post.review_decidedAprovação decidida | Um revisor aprovou ou pediu ajustes | post |
| Contas conectadas | ||
integration.disconnectedConta desconectada | Uma conta conectada perdeu o acesso: a autorização expirou ou caiu | integration |
integration.reconnectedConta reconectada | Uma conta conectada voltou a funcionar | integration |
Particularidades
Um aviso por operação, com o estado final
Criar ou mexer num post várias vezes na mesma operação gera um aviso só, com o estado final. previous_attributes traz o valor de antes da operação inteira.
post.created já vem completo
Ele traz o post inteiro, então as alterações da mesma operação não geram post.updated à parte.
post.scheduled só na entrada da fila
Conta só quando o post sai de rascunho ou de aprovação. Um post com falha que volta para a fila numa nova tentativa não gera um agendamento novo.
post.failed traz o código da falha
data.object.failure.code é um código padronizado (ver Códigos de falha). Não há mensagem: traduza ou decida a ação pelo código.
post.review_decided traz a decisão à parte
data.decision é approved ou request_changes. Ela vem fora do objeto porque, quando a aprovação já agenda o post, ele sai de aprovação no mesmo instante e data.object.review_status passa a ser null, e aí chega também um post.scheduled.
post.deleted traz só a identificação
id, asset_group_id, project, network e deleted_at: o post não responde mais na API.
URLs de mídia sempre válidas
As URLs de mídia são assinadas de novo a cada tentativa, então um reenvio nunca chega com link vencido. Elas expiram em 48 horas, como na API.
O envelope
Todo aviso é um POST com corpo JSON neste formato, igual para os 9 eventos:
{
"id": "evt_01j9x7qk2p8vyq4h5t3m6n0rzb",
"object": "event",
"type": "post.published",
"api_version": "v1",
"created": 1790236812,
"livemode": true,
"data": {
"object": { "id": 184931, "status": "published", "...": "..." },
"previous_attributes": { "status": "publishing", "published_url": null }
}
}| Campo | Descrição |
|---|---|
| id | Identificador do evento. É o mesmo em todas as tentativas: use-o para descartar repetidos |
| object | Sempre event |
| type | Um dos 9 eventos |
| api_version | Versão do formato do objeto: v1, a mesma da API |
| created | Momento do evento, em Unix timestamp. As datas dentro de data.object seguem o fuso do projeto, como no resto da API |
| livemode | false só no evento de teste, enviado pelo botão Enviar teste |
| data_object | O recurso depois da mudança: o post (post.*) ou a conta conectada (integration.*), no mesmo formato da API |
| data_previous_attributes | O valor anterior de cada campo que mudou. Só nos eventos de mudança |
Cabeçalhos
| Nome | Valor |
|---|---|
| Content-Type | application/json |
| User-Agent | Flux-Webhooks/1.0 |
| Flux-Signature | t=1790236812,v1=5257a869e7ec… |
Exemplos por evento
O post vem igual ao GET /api/v1/posts/{id}, bloco review incluído; a conta conectada, igual ao GET /api/v1/integrations.
{
"id": "evt_01j9x7qk2p8vyq4h5t3m6n0rzb",
"object": "event",
"type": "post.published",
"api_version": "v1",
"created": 1790326812,
"livemode": true,
"data": {
"object": {
"id": 184931,
"asset_group_id": 71204,
"project": { "id": 3312, "name": "Padaria Aurora", "timezone": "America/Sao_Paulo" },
"network": "instagram",
"network_group": "instagram",
"account": { "id": "17841404295825681", "name": "padariaaurora" },
"type": "feed",
"status": "published",
"review_status": null,
"caption": "O pão na pedra sai às 7h.",
"first_comment": "#padaria",
"schedule_at": "2026-09-25T07:00:00-03:00",
"published_url": "https://www.instagram.com/p/DAbC123xYz/",
"media": [
{
"id": 998812,
"type": "image",
"url": "https://midias.reportei.com/flux-upload/...",
"url_expires_at": "2026-09-27T07:00:12-03:00",
"width": 1080,
"height": 1350,
"aspect_ratio": "4:5"
}
],
"created_at": "2026-09-21T09:22:10-03:00",
"updated_at": "2026-09-25T07:00:12-03:00",
"review": null
},
"previous_attributes": {
"status": "publishing",
"published_url": null
}
}
}Verificar a assinatura
Cada aviso leva o cabeçalho Flux-Signature, no mesmo formato do Stripe-Signature. Qualquer biblioteca de verificação do Stripe se adapta trocando o nome do cabeçalho:
Flux-Signature: t=1790236812,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdv1 é o HMAC-SHA256, em hexadecimal, de "{t}.{corpo}" com o segredo do webhook (whsec_…). Para conferir:
- 1Leia o corpo bruto, antes de qualquer parse de JSON. Reserializar muda os bytes e a assinatura deixa de bater.
- 2Calcule o HMAC e compare com cada v1 do cabeçalho em tempo constante. Durante a troca de segredo chegam dois v1, o novo e o antigo, por 24 horas: aceite se qualquer um bater.
- 3Recuse se t estiver a mais de 5 minutos do seu relógio: isso protege contra o reenvio malicioso de um aviso antigo.
function fluxSignatureIsValid(string $body, string $header, string $secret): bool
{
parse_str(str_replace(',', '&', $header), $parts);
preg_match_all('/v1=([a-f0-9]{64})/', $header, $signatures);
$timestamp = (int) ($parts['t'] ?? 0);
if (abs(time() - $timestamp) > 300) {
return false;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $body, $secret);
foreach ($signatures[1] as $signature) {
if (hash_equals($expected, $signature)) {
return true;
}
}
return false;
}
$valid = fluxSignatureIsValid(file_get_contents('php://input'), $_SERVER['HTTP_FLUX_SIGNATURE'] ?? '', $secret);Guarde o segredo como uma senha
Quem tem o segredo consegue forjar avisos em nome do Flux. Não o coloque em código versionado; se ele vazar, use Trocar segredo.
Entrega e tentativas
O Flux considera entregue só a resposta 2xx. Qualquer outra coisa conta como falha e entra no ciclo de tentativas.
Responda 2xx em até 10 segundos
Qualquer outra resposta, timeout ou erro de conexão conta como falha. Guarde o aviso, responda e faça o trabalho pesado depois.
Redirecionamento não é seguido
Uma resposta 3xx conta como falha. Cadastre o endereço final.
A ordem de chegada não é garantida
Se a ordem importar, compare created ou consulte o recurso na API.
410 desativa na hora
Responder 410 Gone desativa o webhook sem novas tentativas. É o jeito de o seu sistema pedir para parar de receber.
Identifique o Flux pelo User-Agent
Os avisos saem com User-Agent Flux-Webhooks/1.0, mas quem prova a origem é a assinatura, não o cabeçalho.
Tentativas
Uma entrega que falha é reenviada até 8 vezes, em cerca de 3 dias:
| Tentativa | Quando |
|---|---|
| 1 | Assim que o evento acontece |
| 2 | 5 min depois da anterior |
| 3 | 30 min depois da anterior |
| 4 | 2 h depois da anterior |
| 5 | 6 h depois da anterior |
| 6 | 12 h depois da anterior |
| 7 | 24 h depois da anterior |
| 8 | 24 h depois da anterior |
Endereço que só falha
24 horas sem sucesso: aviso por e-mail
Quem criou o webhook e o dono da conta recebem um e-mail avisando que o endereço está falhando.
3 dias sem sucesso: webhook desativado
O webhook é desativado e um novo e-mail é enviado às mesmas pessoas.
Reativar não reenvia o passado
Reative pela tela. Os avisos do período em que o webhook ficou desativado não são reenviados. Consulte a API para recuperar o estado.
O mesmo aviso pode chegar mais de uma vez
Um timeout do seu lado depois de processar o aviso faz o Flux tentar de novo. Descarte repetidos pelo id do evento, que é o mesmo em todas as tentativas.
Boas práticas
Verifique a assinatura em todo aviso
Com o corpo bruto e a tolerância de 5 minutos. Aviso sem assinatura válida não veio do Flux.
Responda rápido e processe depois
Enfileire o aviso e responda 2xx; 10 segundos é o limite.
Seja idempotente
Guarde o id dos eventos já processados e ignore os repetidos.
Assine só o que você usa
Menos eventos e menos projetos significam menos tráfego e menos avisos para descartar.
Use o teste antes de ir para produção
Enviar teste mostra na hora o status e a resposta do seu endereço, com livemode false para você separar do tráfego real.
Códigos de falha
O data.object.failure.code de um post.failed é um destes códigos. A lista é estável: tratamento condicional por código é seguro.
Dois códigos pedem atenção
not_approved indica um post que chegou à hora de publicar sem a aprovação exigida. É regra do Flux, não erro da rede. unknown_error indica uma falha que não foi classificada.