Configurando webhooks

Registre sua URL, receba o standard_webhooks_secret e valide o primeiro evento. Esta é a página de entrada da seção: o formato dos eventos, a segurança e a reentrega têm páginas próprias, apontadas daqui.

Toda confirmação de mudança de estado (aprovação, aquisição, baixa, recompra, renegociação) chega por webhook. Os endpoints ficam no serviço webhook-sender-manager, em host próprio, e exigem o header iorq-fund-id além do Authorization.

AmbienteBase URL
Produçãohttps://webhook-sender-manager-production.iorq.com.br/api
Sandboxhttps://webhook-sender-manager-development.iorq.com.br/api

O ciclo inteiro, do registro ao replay:

sequenceDiagram
    participant I as Seu sistema
    participant W as IORQ (webhooks)
    I->>W: POST /api/webhook
    W-->>I: 200 + standard_webhooks_secret (só nesta resposta)
    Note over W: um fato acontece (ex.: operação aprovada)
    W->>I: POST webhook_url — webhook-id, webhook-timestamp, webhook-signature
    alt 2xx em até 10 s
        I-->>W: 2xx
        Note over W: entregue
    else erro, timeout ou redirect
        I-->>W: falha
        W->>I: até 10 retentativas (11 tentativas, ~70 h)
        Note over I,W: esgotou: POST /api/events/:event_id/replay
    end

1. Pré-requisitos

  • Endpoint HTTPS público com certificado de CA pública (TLS 1.2+). http:// e self-signed são recusados.
  • A URL responde diretamente, sem redirect. A IORQ não segue redirect: um 3xx conta como falha e entra na reentrega, como qualquer resposta fora de 2xx. Registre a URL final.
  • Responde 2xx em menos de 10 segundos. Enfileire o evento e responda; processar antes de responder é a causa mais comum de reentrega.
  • Token Bearer JWT e o UUID do fundo para o header iorq-fund-id (ver Autenticação).

2. Registrar a URL

POST /api/webhook · application/json

CampoObrigatórioValor
message_typesimLOAN_UPDATE, o único aceito hoje
webhook_urlsimURL https:// que recebe os eventos
contract_versionnãov2 (envelope tipado, o padrão) ou v1 (legado)
event_typesnãoTipos assinados; padrão ["*"]
v1_failure_statusnãoSó para v1: per_context (padrão) ou shared_invalid. Ver abaixo
headersnãoHeaders custom reenviados em cada entrega. Só de escrita: as respostas trazem só os nomes, em header_names
max_attemptsnãoTentativas de entrega por evento, de 1 a 30, contando a primeira. Omitido, vale a política da IORQ, hoje 11 tentativas (a primeira entrega e 10 retentativas) em cerca de 70 h

Sobre os campos opcionais:

  • contract_version. Omitido, o webhook nasce v2, em sandbox e em produção: é o formato descrito no Catálogo de eventos. Em sandbox o envelope chega nativo; em produção, remontado a partir do v1 até a virada de cada produtor (seção 3.1). Não há data de virada a combinar para registrar v2; ela só interessa a quem já consome v1 e vai migrar (seção 4). Até 19/10/2026 o padrão era v1; um webhook já registrado mantém a versão que tem. Quem ainda precisa do corpo legado manda "contract_version": "v1", que segue aceito e está preservado em Contrato v1 (legado).
  • event_types. Aceita *, recurso.* com um dos oito recursos do catálogo (loan, acquisition, installment, prepayment, charge, repurchase, renegotiation, webhook) ou o tipo exato, como installment.settled ou webhook.test. Lista vazia é recusada. O filtro só vale para fatos que o produtor já publica em v2; até a virada, os demais chegam sem passar por ele (ver Assinatura por tipo).
  • headers. Segunda barreira, como um header secreto que você valida do seu lado. A defesa autoritativa é a assinatura HMAC, não eles. Os valores são só de escrita: depois de gravados, nenhuma resposta os devolve, nem a do próprio registro; POST, GET, a listagem, PATCH e a rotação de secret trazem só os nomes, em ordem alfabética, em header_names. Guarde o valor do seu lado. Para trocar um valor, mande PATCH /api/webhook/{webhook_id} com o conjunto completo: headers no PATCH substitui o conjunto inteiro, e um nome ausente deixa de ser enviado; um PATCH sem headers mantém os gravados, e "headers": null remove todos. Até 32 headers, cada nome um token da RFC 9110 e cada valor sem caractere de controle e com até 4 KiB. Alguns nomes pertencem ao transporte ou à IORQ e são recusados com 422, sem diferenciar maiúsculas: Host, Content-Length, Transfer-Encoding, Connection, Keep-Alive, TE, Trailer, Upgrade, Content-Type, webhook-id, webhook-timestamp, webhook-signature, qualquer Proxy-* e qualquer X-IORQ-*.
  • max_attempts. Quantas vezes a IORQ tenta entregar cada evento antes de desistir, seguindo a agenda de esperas de Idempotência e reentrega. Mande o campo só se quiser um número diferente do nosso: sem ele, o seu webhook acompanha a política da IORQ quando ela mudar. O GET sempre mostra o número que vale.
  • v1_failure_status. Só tem efeito em webhook v1. per_context, o padrão, entrega a falha de liquidação, antecipação e recompra com valor próprio em data.status (invalid_settlement, invalid_prepayment, invalid_repurchase). shared_invalid entrega as três como invalid, o comportamento anterior. Um webhook registrado antes deste campo existir está em shared_invalid e continua recebendo invalid até você trocar com PATCH /api/webhook/{webhook_id} e {"v1_failure_status": "per_context"}. Ver Contrato v1 (legado).
curl -X POST 'https://webhook-sender-manager-production.iorq.com.br/api/webhook' \
  -H 'Authorization: Bearer eyJhbGciOi...' \
  -H 'iorq-fund-id: 374485cf-f6df-467c-b91d-9f14082c6f36' \
  -H 'Content-Type: application/json' \
  -d '{
    "message_type": "LOAN_UPDATE",
    "webhook_url": "https://api.meudominio.com/iorq-webhooks",
    "contract_version": "v2",
    "event_types": ["installment.*", "charge.issued"],
    "headers": { "X-My-Auth": "meu-token" }
  }'

Resposta 200 (não 201):

{
  "webhook_id": "01K4Q8ZJ0000000000000000WH",
  "fund_id": "374485cf-f6df-467c-b91d-9f14082c6f36",
  "user_id": "3k9f2a7b1c4d8e6f0g5h2j7k1m",
  "message_type": "LOAN_UPDATE",
  "webhook_url": "https://api.meudominio.com/iorq-webhooks",
  "status": "ACTIVE",
  "header_names": ["X-My-Auth"],
  "max_attempts": 11,
  "suspended_at": null,
  "suspension_reason": null,
  "contract_version": "v2",
  "event_types": ["installment.*", "charge.issued"],
  "v1_failure_status": "per_context",
  "previous_secret_expires_at": null,
  "failing_since": null,
  "created_at": "2026-05-13T12:00:00.734380",
  "updated_at": "2026-05-13T12:00:00.734380",
  "signing_secret": "whsec_K8nP3qR7sT2vW9xY1zA4bC6dE0fG5hJ",
  "standard_webhooks_secret": "whsec_d2hzZWNfSzhuUDNxUjdzVDJ2Vzl4WTF6QTRiQzZkRTBmRzVoSg=="
}
🚧

Guarde o standard_webhooks_secret agora

Ele aparece só nesta resposta, ao lado do signing_secret, que é o mesmo segredo no formato da assinatura legada. GET, PATCH e a listagem não devolvem nenhum dos dois. Quem perde o secret gera outro com a rotação, sem recriar o webhook. É com ele que você valida a assinatura de cada entrega (Configuração e segurança).

Três detalhes da resposta que costumam surpreender:

  • O webhook pertence à credencial (client_id, devolvido em user_id) que o registrou. Outra credencial do mesmo fundo não o vê nem o altera, e cada credencial com webhook ativo recebe a sua própria entrega de cada fato.
  • created_at e updated_at vêm em horário de São Paulo, sem indicador de fuso, com microssegundos. Não os leia como UTC. Os demais campos de data do serviço são UTC com offset; a tabela de relógios está em Consultar eventos e tentativas.
  • Registrar a mesma webhook_url de novo com a mesma credencial devolve 409. Para mudar um webhook existente, use o PATCH.
  • max_attempts mostra o número que vale para o webhook, o seu ou o da política da IORQ, nunca nulo. v1_failure_status só tem efeito num webhook v1, mas vem em todo webhook. failing_since e os campos de suspensão são descritos em Gerenciar.

2.1 Erros

O serviço de webhooks devolve erro em dois formatos, conforme a camada que recusou a chamada:

CamadaCorpoQuando
Gateway{"message": "Unauthorized"} (401) / {"message": "Forbidden"} (403)Authorization ausente / token inválido ou expirado. A chamada não chega ao serviço
Serviço{"code": "...", "message": "...", "details": ...}Todo o resto: header, autorização, validação, recurso inexistente, conflito de estado, limite

No formato do serviço, code é um identificador estável em snake_case minúsculo: programe contra ele, não contra a message, que é texto livre em inglês. details só vem preenchido em dois casos: no validation_error, a lista [{"loc", "msg", "type"}], uma entrada por campo recusado, sem o valor recebido; e no event_already_replayed, {"replayed_to_event_id": "..."}. Nos demais, null. Exemplo:

{
  "code": "webhook_not_found",
  "message": "Webhook not found",
  "details": null,
  "error_code": "WEBHOOK_NOT_FOUND",
  "status": 404
}
🚧

error_code e status saem em 11/11/2026

O corpo ainda leva dois campos de compatibilidade: error_code, o mesmo code em maiúsculas (WEBHOOK_NOT_FOUND), e status, o status HTTP como inteiro. Eles existem para não quebrar quem lia o formato anterior e serão removidos em 11/11/2026. Leia code. O antigo corpo {"detail": ...} não volta.

Os code que o serviço emite:

codeHTTPQuando
bad_request400Header iorq-fund-id ausente, fora do formato UUID, ou divergente de Fund-Id (ver abaixo)
invalid_cursor400next_page_token alterado, truncado ou que não foi emitido pelo serviço de webhooks (Paginação)
invalid_request400Cursor assinado pelo serviço que o banco de dados recusa (pode acontecer com o cursor de uma listagem repassado a outra)
unauthorized401Token que passou pelo gateway e foi recusado pelo serviço. Raro: o caso comum é o 401 do gateway
forbidden403Credencial sem acesso ao fundo, ou sem a permissão da rota (a message diz User does not have required permission: wsm_...)
webhook_not_found404webhook_id que não existe para esta credencial
event_not_found404event_id que não existe para esta credencial
attempt_not_found404attempt_id que não existe para esta credencial
webhook_already_exists409webhook_url já registrada por esta credencial
webhook_not_deliverable409Evento de teste ou replay (por intervalo ou por event_id) num webhook fora de ACTIVE
webhook_concurrent_update409PATCH ou rotação de secret enquanto o status do webhook mudava (por exemplo, a IORQ o suspendeu no meio da chamada): leia o webhook de novo e repita
event_not_replayable409Replay por event_id sem o que reentregar: a última tentativa foi bem-sucedida, a reentrega ainda está agendada (aguarde: a IORQ ainda vai tentar), ou o evento ainda não teve nenhuma tentativa (use o replay por intervalo)
event_already_replayed409Replay por event_id de um evento que já foi reenviado e cujo clone já teve tentativa. O id do clone vem em details.replayed_to_event_id: acompanhe por ele
validation_error422Corpo ou parâmetro inválido. A message traz uma linha campo: motivo por campo, e details a lista
webhook_destination_not_allowed422webhook_url cujo host resolve para um endereço que não é público
webhook_limit_reached422O registro ou a reativação passaria do limite de webhooks ativos por fundo, credencial e message_type (hoje 5): inative um antes
webhook_test_rate_limited429Mais de 10 eventos de teste no webhook na última hora
internal_error500Falha inesperada do serviço, com mensagem genérica. Repita com backoff
auth_keys_unavailable503O serviço não conseguiu ler as chaves de assinatura dos tokens. Falha de infraestrutura, não do seu token: repita com backoff
service_unavailable503O serviço não conseguiu consultar as permissões da credencial. Também transitória: repita com backoff

Por status:

  • 400 — header iorq-fund-id ausente ou fora do formato UUID (bad_request), ou cursor de paginação inválido (invalid_cursor, invalid_request). Fund-Id é o nome legado deste mesmo header: o serviço ainda o aceita como fallback para integrações antigas, mas o padrão é iorq-fund-id, e uma integração nova não deve enviá-lo. Se os dois vierem com valores diferentes, a resposta é 400, nunca a escolha silenciosa de um deles. Esta é a única menção ao nome legado nesta documentação.
  • 401 — quando o header Authorization está ausente, o gateway responde {"message": "Unauthorized"}. Diferente da API de cessão, aqui token expirado não é 401: é o 403 abaixo.
  • 403 — dois casos, distinguidos pelo corpo. Só com message ({"message": "Forbidden"}), é o gateway recusando um token inválido ou expirado antes de a chamada chegar ao serviço: renove o token e repita. Com code: "forbidden", é o serviço: a credencial não tem acesso ao fundo (fundo inexistente e fundo de outro cliente recebem a mesma resposta), ou não tem a permissão da rota, caso em que a message diz User does not have required permission: wsm_... e a permissão se pede ao seu contato na IORQ (ver Permissões).
  • 404 — webhook_id, event_id ou attempt_id que não existe para esta credencial (webhook_not_found, event_not_found, attempt_not_found).
  • 409 — webhook_url já registrada por esta credencial (webhook_already_exists); replay por event_id sem o que reentregar (event_not_replayable) ou de um evento já reenviado (event_already_replayed); evento de teste ou replay num webhook fora de ACTIVE, seja INACTIVE ou SUSPENDED (webhook_not_deliverable); PATCH ou rotação enquanto o status mudava (webhook_concurrent_update).
  • 429 — mais de 10 eventos de teste no mesmo webhook na última hora (webhook_test_rate_limited).
  • 422 — validation_error para corpo ou parâmetro inválido: campo obrigatório ausente, webhook_url sem https://, message_type fora de LOAN_UPDATE, contract_version fora de v1/v2, v1_failure_status fora de per_context/shared_invalid, event_types vazio ou com padrão desconhecido, max_attempts fora de 1..30, headers fora das regras ou com nome proibido, status igual a SUSPENDED no PATCH, expires_in_hours fora de 1..72 na rotação, limit fora de 1..200 nas listagens, ou, no replay por intervalo, date_from/date_to sem fuso ou fora de ISO-8601, ou date_to anterior ou igual a date_from. O msg de cada item lista os valores aceitos. Também 422, com code próprio, o destino que não é público (webhook_destination_not_allowed) e o limite de webhooks ativos (webhook_limit_reached).
  • 500 / 503 — falha do serviço ou de uma dependência dele (internal_error, auth_keys_unavailable, service_unavailable), nunca do seu pedido. Repita com backoff; um 503 não significa que a credencial perdeu acesso.

3. Validar o primeiro evento

Confira o que ficou gravado, inclusive contract_version, event_types e v1_failure_status:

curl 'https://webhook-sender-manager-production.iorq.com.br/api/webhook' \
  -H 'Authorization: Bearer eyJhbGciOi...' \
  -H 'iorq-fund-id: 374485cf-f6df-467c-b91d-9f14082c6f36'

Depois, peça um evento de teste para a URL registrada:

curl -X POST 'https://webhook-sender-manager-production.iorq.com.br/api/webhook/01K4Q8ZJ0000000000000000WH/test' \
  -H 'Authorization: Bearer eyJhbGciOi...' \
  -H 'iorq-fund-id: 374485cf-f6df-467c-b91d-9f14082c6f36'

A resposta é 202 com {"event_id": "...", "webhook_id": "..."}. O evento é um webhook.test (num webhook v1, data.status igual a test), só para este webhook, e percorre a entrega normal: assinatura, tentativas, replay. Ele não passa pelo filtro event_types: chega mesmo num webhook que assina só installment.*. Limite de 10 por webhook na última hora, em janela deslizante (429); num webhook fora de ACTIVE (INACTIVE ou SUSPENDED) a resposta é 409 com webhook_not_deliverable. A chamada exige a permissão wsm_send_test_event; sem ela, 403. Até 11/11/2026 a rota também aceita a permissão anterior, wsm_create_event; depois dessa data, só wsm_send_test_event. Ver o verbete webhook.test, com o corpo v2 completo. Quando ele chegar, ou quando chegar o primeiro fato real (o Quickstart leva você do registro à primeira cessão e à primeira baixa), faça três coisas, nesta ordem:

  1. Valide a assinatura webhook-signature sobre os bytes recebidos, com o standard_webhooks_secret (spec Standard Webhooks). Código em Python e Node.js em Configuração e segurança.
  2. Leia o type (v2) ou o data.status (v1) para saber o que aconteceu. A lista está no Catálogo de eventos, e o que fazer com cada um em Tipos de evento em detalhe.
  3. Grave a idempotency_key antes de processar: a entrega é at-least-once. Ver Idempotência e reentrega.

Se nada chegar, o roteiro é curto:

  1. A chamada respondeu 202 mas nada chegou à sua URL? Consulte GET /api/attempts/event/{event_id} com o event_id da resposta. response_status: 0 significa que a IORQ não alcançou a sua URL (DNS, conexão recusada, TLS, timeout de 10 s): o problema está entre a IORQ e o seu endpoint (firewall, certificado, URL errada), não no seu código. Um response_status preenchido é o que o seu endpoint devolveu; response_body traz o que ele escreveu.
  2. Nem 202 você tem? Leia o erro: 403 é token ou permissão, 409 é webhook fora de ACTIVE, 429 é o limite do teste (seção 2.1).
  3. Para um fato real, e não um teste, GET /api/events/webhook/{webhook_id} mostra se a IORQ chegou a gerar o evento para o seu webhook; se gerou, o passo 1 vale igual. Os campos dos dois endpoints estão em Consultar eventos e tentativas.

3.1 O que muda entre sandbox e produção

Esta é a única descrição das diferenças; as outras páginas apontam para cá. O contrato é o mesmo nos dois ambientes, mas dois fatos mudam o que o seu webhook recebe:

  • Em sandbox os produtores já publicam v2. Um webhook v2 recebe o envelope nativo: data.loan.id preenchido, o id do envelope estável entre as tentativas, os headers X-IORQ-Event-Id, X-IORQ-Event-Type e X-IORQ-Delivery-Id em toda entrega, o filtro event_types aplicado, e GET /api/events/webhook/{webhook_id} devolvendo o envelope em payload com schema_version: "2026-10-01".
  • Em produção os produtores ainda publicam v1, e a IORQ remonta o envelope v2 a partir dessa mensagem na entrega. Os campos e os tipos são os mesmos, mas data.loan.id vem nulo, os headers de identificação X-IORQ-* não chegam (o webhook-id chega), o id do envelope muda a cada entrega, os fatos não passam pelo filtro event_types, e a listagem de eventos devolve a mensagem v1 armazenada. A lista completa, com o que fazer em cada caso, está em O que muda durante a transição.

A consequência prática: o teste em sandbox exercita o envelope nativo; em produção o mesmo webhook recebe o envelope remontado. Concilie pelo data.loan.originator_code, deduplique pela idempotency_key e não construa roteamento que dependa de data.loan.id ou dos headers de identificação X-IORQ-* até a virada dos produtores em produção.

A outra diferença não é do serviço de webhooks, e sim do ambiente: sandbox não tem administradora, então os eventos que dependem da confirmação dela não existem lá. Ver Ambientes e hosts.

4. Migrar um webhook v1 para o v2

Duas formas, e a segunda é a que recomendamos para quem já está em produção:

  1. Trocar a versão no lugar, com PATCH /api/webhook/{webhook_id} e {"contract_version": "v2"}. Simples, porém sem rede: a partir da próxima entrega o corpo muda.
  2. Registrar um segundo webhook em v2, apontando para um endpoint novo, e deixar os dois ativos. Os dois recebem os mesmos fatos, cada um com o seu signing_secret, e você compara os dois lados pela idempotency_key. Quando estiver convencido, inative o v1 com PATCH e {"status": "INACTIVE"}. Se algo der errado, reative com o mesmo PATCH.

Antes de cortar o v1 em produção, leia o aviso "Onde o v2 já roda" no Catálogo de eventos: em produção o envelope v2 ainda é reconstruído a partir da mensagem v1. O completed legado, o único status v1 sem tipo v2, já não é emitido por nenhum produtor; se voltasse, chegaria só a webhooks v1 (ver Contrato v1 (legado)). É para esse corte, de quem já consome v1, que vale combinar a data de virada com o seu contato na IORQ.

5. Gerenciar

ParaChamada
Listar webhooks da credencial no fundoGET /api/webhook
Ver um webhookGET /api/webhook/{webhook_id}
Trocar URL, headers, versão ou filtroPATCH /api/webhook/{webhook_id}
Pausar / reativarPATCH com {"status": "INACTIVE"} / {"status": "ACTIVE"}
Recuperar tudo de um período, depois de uma queda ou suspensãoPOST /api/events/replay, 202 (permissão wsm_replay_event)
Rotacionar o signing_secret sem janela de rejeiçãoPOST /api/webhook/{webhook_id}/secret/rotate (permissão wsm_update_webhook)
Disparar um evento de teste, até 10 por horaPOST /api/webhook/{webhook_id}/test (permissão wsm_send_test_event)
Ver eventos geradosGET /api/events/webhook/{webhook_id}
Ver um eventoGET /api/events/{event_id}
Ver tentativas de um eventoGET /api/attempts/event/{event_id}
Ver uma tentativaGET /api/attempts/{attempt_id}
Reenviar um evento específico que esgotou as tentativasPOST /api/events/{event_id}/replay, 201 com o clone (permissão wsm_replay_event)

O campo status tem três valores, e só dois são seus:

statusQuem atribuiO que significa
ACTIVEvocêRecebendo entregas
INACTIVEvocêPausado por você; nada é entregue, e os fatos que acontecem durante a pausa não geram evento para este webhook
SUSPENDEDa IORQTrês dias ou mais sem nenhuma entrega com 2xx. Os eventos continuam sendo gerados e guardados

Saúde do webhook: failing_since. GET, a listagem, PATCH e POST devolvem o campo, só de leitura: o instante, em UTC ISO-8601 com offset, em que o webhook começou a falhar, ou null quando ele está saudável. O relógio abre na primeira entrega sem 2xx quando não há relógio aberto, inclusive na primeira entrega da vida do webhook, se ela falhar (um webhook recém-registrado com a URL errada já nasce com failing_since preenchido na primeira tentativa), e fecha na próxima entrega com 2xx, voltando a null. Se passam mais de 36 horas sem nenhuma falha (a maior espera entre tentativas, 24 horas, mais uma folga), a próxima falha reabre o relógio a partir dela: silêncio não é prova de endpoint fora do ar. Para a saúde, qualquer resposta fora de 2xx conta como falha, inclusive 3xx e 4xx.

Suspensão. Esta é a descrição completa das regras; as outras páginas apontam para cá. A IORQ coloca o webhook em SUSPENDED, preenche suspended_at e grava em suspension_reason qual regra disparou, em três situações:

  1. Três dias sem 2xx. Uma entrega falha e failing_since tem três dias ou mais. Independe de max_attempts, e um 4xx contínuo conta: recusar tudo o que chega, por três dias seguidos com tráfego, também suspende. suspension_reason: 3 dias sem entrega bem-sucedida.
  2. Agenda esgotada com o endpoint indisponível. Um evento esgota todas as tentativas, o webhook não teve nenhum 2xx desde antes da primeira delas (com pelo menos 24 horas de falha) e a última resposta indica endpoint indisponível: sem resposta, 3xx, 5xx, 408 ou 429. Com a agenda padrão, cerca de 70 horas. suspension_reason: agenda de reentrega esgotada sem entrega bem-sucedida (71 h).
  3. 4xx não abre a suspensão antecipada, salvo 408 e 429. É o seu endpoint de pé recusando um corpo, e um único evento recusado num fim de semana sem outro tráfego não desliga um webhook saudável. Um endpoint que recusa um corpo específico com 4xx e aceita as outras entregas volta a failing_since: null a cada sucesso e não é suspenso; só é suspenso quem passa três dias sem nenhum sucesso (regra 1).

Para um corpo autenticado que você não quer processar, responda 2xx e trate o descarte do seu lado; para assinatura inválida, 401 (Configuração e segurança).

Monitore do seu lado. A suspensão ainda não gera e-mail automático: a IORQ é alertada e avisa você pelo seu contato na IORQ, mas não construa o seu alerta em cima disso. Duas formas baratas, e a primeira basta:

  • Consulte GET /api/webhook/{webhook_id} periodicamente, por exemplo uma vez por hora, e alerte quando status sair de ACTIVE ou failing_since deixar de ser null. O campo abre na primeira falha, então o alerta chega dias antes da suspensão.
  • Conte, do seu lado, as respostas não-2xx que o seu endpoint devolveu às entregas da IORQ e alerte quando passarem de N num intervalo (por exemplo, 5 em 15 minutos). Pega o problema sem depender de chamada à IORQ.

Reativar. Faça PATCH com {"status": "ACTIVE"}, o que limpa suspended_at, suspension_reason e failing_since. Só a mudança de status zera o relógio: um PATCH que reenvia o status já vigente, como num objeto completo, não mexe em failing_since. Reativar não reenvia nada sozinho: depois do PATCH, recupere o período com o replay por intervalo, com a permissão wsm_replay_event. Ele devolve os eventos gerados durante a suspensão e também os que estavam no meio da reentrega quando ela começou. Mandar {"status": "SUSPENDED"} você mesmo é recusado com 422: suspender é decisão da IORQ.

Consultar eventos e tentativas

GET /api/events/webhook/{webhook_id} lista os eventos gerados para o webhook, do mais novo para o mais velho, com date_from/date_to opcionais sobre a data de criação, limit e next_page_token (Paginação). Cada item traz event_id, message_type, external_id, payload, schema_version, replayed_from_event_id (o original, quando o evento nasceu de um replay) e created_at.

Dois cuidados com o payload:

  • É a mensagem como foi armazenada, não necessariamente o corpo que chegou a você. schema_version diz qual: null é a mensagem v1 que o produtor publicou ({"event", "data"}, sem o iorq_fund_id e o event_timestamp que a entrega injeta); "2026-10-01" é um envelope v2. Para um webhook v2 em produção, onde os produtores ainda publicam v1 (ver Catálogo de eventos), a listagem devolve a mensagem v1 armazenada, enquanto o seu endpoint recebeu o envelope v2 remontado na entrega. O corpo exatamente como foi enviado está em cada tentativa, abaixo.
  • webhook.test e os fatos que o produtor já publica em v2 aparecem com schema_version: "2026-10-01" e o envelope em payload, para qualquer versão de webhook.

GET /api/attempts/event/{event_id} lista as tentativas de entrega do evento, uma por POST feito à sua URL. Cada item traz attempt_id, attempt_number, webhook_url, status (SUCCESS ou FAILED), response_status, response_body, is_final, payload e created_at:

CampoO que é
response_statusO status HTTP que o seu endpoint devolveu. 0 quando a IORQ não chegou a receber resposta: DNS, conexão recusada, TLS, timeout de 10 s
is_finaltrue na última tentativa do evento, seja porque deu 2xx, seja porque a agenda esgotou. Uma tentativa FAILED com is_final: false ainda tem reentrega agendada
payloadO corpo enviado nesta tentativa, byte a byte, já na versão de contrato do seu webhook. É aqui que você vê o que chegou, ou deveria ter chegado, à sua URL
created_atO instante da tentativa, em UTC ISO-8601 com offset

GET /api/events/{event_id} devolve um único evento, com os mesmos campos da listagem. GET /api/attempts/{attempt_id} devolve uma única tentativa, pelo attempt_id que a listagem traz, com os mesmos campos dela menos attempt_id, attempt_number e webhook_url, e com o webhook_id no lugar. Os dois respondem 404 quando o identificador não existe para esta credencial no fundo (event_not_found no evento, attempt_not_found na tentativa).

Relógios. Os campos de data do serviço não estão todos no mesmo fuso, e esta é a única lista:

CampoFuso
Event.created_at, Attempt.created_at, failing_since, suspended_at, previous_secret_expires_atUTC, ISO-8601 com offset (2026-09-20T03:00:00.123456+00:00)
Webhook.created_at, Webhook.updated_atHorário de São Paulo, sem indicador de fuso, com microssegundos. Não os leia como UTC
date_from/date_to em GET /api/events/webhook/{webhook_id}Aceitos sem fuso e lidos como UTC: 2026-09-20T00:00:00 é meia-noite UTC, 21h do dia anterior em São Paulo. Com fuso, o offset é respeitado
date_from/date_to em POST /api/events/replayExigem fuso; sem ele, 422 (ver replay por intervalo)

Paginação

As três listagens, GET /api/webhook, GET /api/events/webhook/{webhook_id} e GET /api/attempts/event/{event_id}, paginam do mesmo jeito. limit vai de 1 a 200, com padrão 10; fora disso, 422 validation_error. A resposta traz response (os itens da página), count (quantos vieram nela) e next_page_token. Para a página seguinte, repita a chamada com next_page_token igual ao valor recebido, até ele vir null.

O next_page_token é um cursor opaco e assinado pela IORQ: repasse-o exatamente como veio, sem decodificar nem montar o seu. Um cursor alterado, truncado ou que não foi emitido pelo serviço de webhooks é recusado com 400 invalid_cursor, e o de uma listagem repassado a outra pode ser recusado com 400 invalid_request. Nos dois casos, recomece da primeira página, sem next_page_token.

Permissões

Cada rota exige uma permissão da credencial. A IORQ concede a role wsm_client_webhook ao criar sua credencial, e ela cobre todas as rotas abaixo. Se receber 403 com required permission no detail, peça ao seu contato na IORQ.

RotaPermissão
POST /api/webhookwsm_create_webhook
GET /api/webhook, GET /api/webhook/{webhook_id}wsm_read_webhook
PATCH /api/webhook/{webhook_id}, POST /api/webhook/{webhook_id}/secret/rotatewsm_update_webhook
POST /api/webhook/{webhook_id}/testwsm_send_test_event (até 11/11/2026, também wsm_create_event)
GET /api/events/webhook/{webhook_id}, GET /api/events/{event_id}wsm_read_event
POST /api/events/{event_id}/replay, POST /api/events/replaywsm_replay_event
GET /api/attempts/event/{event_id}, GET /api/attempts/{attempt_id}wsm_read_attempt

Não há DELETE: um webhook é desativado, não apagado. A referência completa, com o console para testar, está em Registrar URL de webhook.

6. Próximos passos


Did this page help you?