Flux
Flux API v1Servidor MCPWebhooks
Webhooks
Webhooks
Configurar webhooks no Flux
Flux Webhooks v1

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

  1. 1Clique em Criar webhook.
  2. 2Informe o endereço (URL https) que vai receber os avisos e, se quiser, uma descrição.
  3. 3Marque os eventos que esse endereço deve receber (ao menos um).
  4. 4Escolha os projetos: todos os atuais e os próximos, ou uma seleção.
  5. 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.

EventoQuandoObjeto
Ciclo do post
post.createdPost criadoUm post foi criado, pela tela, pela API ou pelo MCPpost
post.updatedPost alteradoMudou a legenda, a data, o tipo, a conta ou o link publicado, sem mudar o statuspost
post.deletedPost excluídoO post foi excluídopost
Publicação
post.scheduledPost agendadoO post saiu de rascunho ou de aprovação e foi agendadopost
post.publishedPost publicadoO post foi publicado na redepost
post.failedFalha na publicaçãoA publicação falhoupost
Aprovação
post.review_decidedAprovação decididaUm revisor aprovou ou pediu ajustespost
Contas conectadas
integration.disconnectedConta desconectadaUma conta conectada perdeu o acesso: a autorização expirou ou caiuintegration
integration.reconnectedConta reconectadaUma conta conectada voltou a funcionarintegration

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 }
  }
}
CampoDescrição
idIdentificador do evento. É o mesmo em todas as tentativas: use-o para descartar repetidos
objectSempre event
typeUm dos 9 eventos
api_versionVersão do formato do objeto: v1, a mesma da API
createdMomento do evento, em Unix timestamp. As datas dentro de data.object seguem o fuso do projeto, como no resto da API
livemodefalse só no evento de teste, enviado pelo botão Enviar teste
data_objectO recurso depois da mudança: o post (post.*) ou a conta conectada (integration.*), no mesmo formato da API
data_previous_attributesO valor anterior de cada campo que mudou. Só nos eventos de mudança

Cabeçalhos

NomeValor
Content-Typeapplication/json
User-AgentFlux-Webhooks/1.0
Flux-Signaturet=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=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

v1 é o HMAC-SHA256, em hexadecimal, de "{t}.{corpo}" com o segredo do webhook (whsec_…). Para conferir:

  1. 1Leia o corpo bruto, antes de qualquer parse de JSON. Reserializar muda os bytes e a assinatura deixa de bater.
  2. 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.
  3. 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:

TentativaQuando
1Assim que o evento acontece
25 min depois da anterior
330 min depois da anterior
42 h depois da anterior
56 h depois da anterior
612 h depois da anterior
724 h depois da anterior
824 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.

access_forbiddenaccess_token_expiredaccess_token_expired_threadsaccess_token_invalidapi_call_limitapi_rate_limitauthentication_failedauthentication_requiredauthorization_failedblocked_linkboard_not_foundcannot_access_mediacannot_access_private_userchannel_closedchar_limit_exceededcontent_policy_violationcontent_violates_threadscontent_violates_youtubedaily_api_limitdaily_api_quota_exceededdescription_too_longduplicate_content_detectedduplicate_postduplicate_post_gmbduplicate_post_threadsduration_check_failedempty_contentfeed_action_limitfile_format_check_failedframe_rate_check_failedidentity_confirmation_requiredimage_size_exceeds_limitimage_too_largeimage_too_smallinstagram_action_blockedinstagram_temporary_access_errorinsufficient_authentication_scopesinsufficient_permissionsinsufficient_permissions_threadsintegration_expiredintegration_not_foundintegration_token_not_foundinvalid_access_tokeninvalid_action_urlinvalid_aspect_ratioinvalid_caption_lengthinvalid_category_idinvalid_data_providedinvalid_descriptioninvalid_image_urlinvalid_media_aspect_ratioinvalid_parameterinvalid_pin_datainvalid_post_datainvalid_requestinvalid_request_formatinvalid_tagsinvalid_thumbnail_formatinvalid_titleinvalid_urninvalid_video_formatlimited_accesslinkedin_permission_deniedlocation_not_foundmax_posts_per_daymedia_already_publishedmedia_errormedia_posted_before_conversionmedia_size_exceededmedia_size_exceeded_threadsmissing_invalid_credentialsnot_approvedoauth_authentication_failurepage_not_linkedpage_request_limitpermission_deniedpermission_denied_gmbpermission_denied_threadsphoto_fetch_failedphoto_invalid_formatphoto_invalid_sizephoto_upload_failpicture_size_check_failedpinterest_permission_deniedpost_body_too_longprocessing_failureproduct_tagging_unavailablepublish_blocked_draft_leftquota_exceededrate_limit_exceededrate_limit_exceeded_youtuberate_limit_threadsscope_not_authorizedspam_contentspam_risk_too_many_postsstory_creation_rate_limitedtemporary_downtimetext_format_check_failedtext_too_longthumbnail_too_largetiktok_internal_errortoo_many_hashtagstoo_many_requeststotal_duration_too_longtrial_reel_not_supportedunknown_errorunsupported_media_typeupload_limit_exceededupload_too_largeuser_access_restricteduser_in_checkpointverify_status_authorization_errorverify_status_exhaustedvideo_duration_exceededvideo_duration_too_longvideo_file_too_largevideo_format_not_supportedvideo_not_foundvideo_size_check_failedvideo_too_largevideo_upload_failvideo_upload_failedwebp_format_not_supported

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.