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.
| Ambiente | Base URL |
|---|---|
| Produção | https://webhook-sender-manager-production.iorq.com.br/api |
| Sandbox | https://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
3xxconta como falha e entra na reentrega, como qualquer resposta fora de2xx. Registre a URL final. - Responde
2xxem 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
| Campo | Obrigatório | Valor |
|---|---|---|
message_type | sim | LOAN_UPDATE, o único aceito hoje |
webhook_url | sim | URL https:// que recebe os eventos |
contract_version | não | v2 (envelope tipado, o padrão) ou v1 (legado) |
event_types | não | Tipos assinados; padrão ["*"] |
v1_failure_status | não | Só para v1: per_context (padrão) ou shared_invalid. Ver abaixo |
headers | não | Headers custom reenviados em cada entrega. Só de escrita: as respostas trazem só os nomes, em header_names |
max_attempts | não | Tentativas 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 nascev2, 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 erav1; 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, comoinstallment.settledouwebhook.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,PATCHe a rotação de secret trazem só os nomes, em ordem alfabética, emheader_names. Guarde o valor do seu lado. Para trocar um valor, mandePATCH /api/webhook/{webhook_id}com o conjunto completo:headersnoPATCHsubstitui o conjunto inteiro, e um nome ausente deixa de ser enviado; umPATCHsemheadersmantém os gravados, e"headers": nullremove 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 com422, sem diferenciar maiúsculas:Host,Content-Length,Transfer-Encoding,Connection,Keep-Alive,TE,Trailer,Upgrade,Content-Type,webhook-id,webhook-timestamp,webhook-signature, qualquerProxy-*e qualquerX-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. OGETsempre mostra o número que vale.v1_failure_status. Só tem efeito em webhookv1.per_context, o padrão, entrega a falha de liquidação, antecipação e recompra com valor próprio emdata.status(invalid_settlement,invalid_prepayment,invalid_repurchase).shared_invalidentrega as três comoinvalid, o comportamento anterior. Um webhook registrado antes deste campo existir está emshared_invalide continua recebendoinvalidaté você trocar comPATCH /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 ostandard_webhooks_secretagoraEle aparece só nesta resposta, ao lado do
signing_secret, que é o mesmo segredo no formato da assinatura legada.GET,PATCHe 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 emuser_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_ateupdated_atvê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_urlde novo com a mesma credencial devolve409. Para mudar um webhook existente, use oPATCH. max_attemptsmostra o número que vale para o webhook, o seu ou o da política da IORQ, nunca nulo.v1_failure_statussó tem efeito num webhookv1, mas vem em todo webhook.failing_sincee 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:
| Camada | Corpo | Quando |
|---|---|---|
| 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_codeestatussaem em 11/11/2026O corpo ainda leva dois campos de compatibilidade:
error_code, o mesmocodeem maiúsculas (WEBHOOK_NOT_FOUND), estatus, o status HTTP como inteiro. Eles existem para não quebrar quem lia o formato anterior e serão removidos em 11/11/2026. Leiacode. O antigo corpo{"detail": ...}não volta.
Os code que o serviço emite:
code | HTTP | Quando |
|---|---|---|
bad_request | 400 | Header iorq-fund-id ausente, fora do formato UUID, ou divergente de Fund-Id (ver abaixo) |
invalid_cursor | 400 | next_page_token alterado, truncado ou que não foi emitido pelo serviço de webhooks (Paginação) |
invalid_request | 400 | Cursor assinado pelo serviço que o banco de dados recusa (pode acontecer com o cursor de uma listagem repassado a outra) |
unauthorized | 401 | Token que passou pelo gateway e foi recusado pelo serviço. Raro: o caso comum é o 401 do gateway |
forbidden | 403 | Credencial sem acesso ao fundo, ou sem a permissão da rota (a message diz User does not have required permission: wsm_...) |
webhook_not_found | 404 | webhook_id que não existe para esta credencial |
event_not_found | 404 | event_id que não existe para esta credencial |
attempt_not_found | 404 | attempt_id que não existe para esta credencial |
webhook_already_exists | 409 | webhook_url já registrada por esta credencial |
webhook_not_deliverable | 409 | Evento de teste ou replay (por intervalo ou por event_id) num webhook fora de ACTIVE |
webhook_concurrent_update | 409 | PATCH 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_replayable | 409 | Replay 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_replayed | 409 | Replay 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_error | 422 | Corpo ou parâmetro inválido. A message traz uma linha campo: motivo por campo, e details a lista |
webhook_destination_not_allowed | 422 | webhook_url cujo host resolve para um endereço que não é público |
webhook_limit_reached | 422 | O 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_limited | 429 | Mais de 10 eventos de teste no webhook na última hora |
internal_error | 500 | Falha inesperada do serviço, com mensagem genérica. Repita com backoff |
auth_keys_unavailable | 503 | O serviço não conseguiu ler as chaves de assinatura dos tokens. Falha de infraestrutura, não do seu token: repita com backoff |
service_unavailable | 503 | O serviço não conseguiu consultar as permissões da credencial. Também transitória: repita com backoff |
Por status:
400— headeriorq-fund-idausente 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 headerAuthorizationestá ausente, o gateway responde{"message": "Unauthorized"}. Diferente da API de cessão, aqui token expirado não é401: é o403abaixo.403— dois casos, distinguidos pelo corpo. Só commessage({"message": "Forbidden"}), é o gateway recusando um token inválido ou expirado antes de a chamada chegar ao serviço: renove o token e repita. Comcode: "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 amessagedizUser does not have required permission: wsm_...e a permissão se pede ao seu contato na IORQ (ver Permissões).404—webhook_id,event_idouattempt_idque não existe para esta credencial (webhook_not_found,event_not_found,attempt_not_found).409—webhook_urljá registrada por esta credencial (webhook_already_exists); replay porevent_idsem o que reentregar (event_not_replayable) ou de um evento já reenviado (event_already_replayed); evento de teste ou replay num webhook fora deACTIVE, sejaINACTIVEouSUSPENDED(webhook_not_deliverable);PATCHou 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_errorpara corpo ou parâmetro inválido: campo obrigatório ausente,webhook_urlsemhttps://,message_typefora deLOAN_UPDATE,contract_versionfora dev1/v2,v1_failure_statusfora deper_context/shared_invalid,event_typesvazio ou com padrão desconhecido,max_attemptsfora de1..30,headersfora das regras ou com nome proibido,statusigual aSUSPENDEDnoPATCH,expires_in_hoursfora de1..72na rotação,limitfora de1..200nas listagens, ou, no replay por intervalo,date_from/date_tosem fuso ou fora de ISO-8601, oudate_toanterior ou igual adate_from. Omsgde cada item lista os valores aceitos. Também422, comcodepró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; um503nã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:
- Valide a assinatura
webhook-signaturesobre os bytes recebidos, com ostandard_webhooks_secret(spec Standard Webhooks). Código em Python e Node.js em Configuração e segurança. - Leia o
type(v2) ou odata.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. - Grave a
idempotency_keyantes de processar: a entrega é at-least-once. Ver Idempotência e reentrega.
Se nada chegar, o roteiro é curto:
- A chamada respondeu
202mas nada chegou à sua URL? ConsulteGET /api/attempts/event/{event_id}com oevent_idda resposta.response_status: 0significa 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. Umresponse_statuspreenchido é o que o seu endpoint devolveu;response_bodytraz o que ele escreveu. - Nem
202você tem? Leia o erro:403é token ou permissão,409é webhook fora deACTIVE,429é o limite do teste (seção 2.1). - 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.idpreenchido, oiddo envelope estável entre as tentativas, os headersX-IORQ-Event-Id,X-IORQ-Event-TypeeX-IORQ-Delivery-Idem toda entrega, o filtroevent_typesaplicado, eGET /api/events/webhook/{webhook_id}devolvendo o envelope empayloadcomschema_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.idvem nulo, os headers de identificaçãoX-IORQ-*não chegam (owebhook-idchega), oiddo envelope muda a cada entrega, os fatos não passam pelo filtroevent_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:
- 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. - 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 seusigning_secret, e você compara os dois lados pelaidempotency_key. Quando estiver convencido, inative o v1 comPATCHe{"status": "INACTIVE"}. Se algo der errado, reative com o mesmoPATCH.
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
| Para | Chamada |
|---|---|
| Listar webhooks da credencial no fundo | GET /api/webhook |
| Ver um webhook | GET /api/webhook/{webhook_id} |
| Trocar URL, headers, versão ou filtro | PATCH /api/webhook/{webhook_id} |
| Pausar / reativar | PATCH com {"status": "INACTIVE"} / {"status": "ACTIVE"} |
| Recuperar tudo de um período, depois de uma queda ou suspensão | POST /api/events/replay, 202 (permissão wsm_replay_event) |
Rotacionar o signing_secret sem janela de rejeição | POST /api/webhook/{webhook_id}/secret/rotate (permissão wsm_update_webhook) |
| Disparar um evento de teste, até 10 por hora | POST /api/webhook/{webhook_id}/test (permissão wsm_send_test_event) |
| Ver eventos gerados | GET /api/events/webhook/{webhook_id} |
| Ver um evento | GET /api/events/{event_id} |
| Ver tentativas de um evento | GET /api/attempts/event/{event_id} |
| Ver uma tentativa | GET /api/attempts/{attempt_id} |
| Reenviar um evento específico que esgotou as tentativas | POST /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:
status | Quem atribui | O que significa |
|---|---|---|
ACTIVE | você | Recebendo entregas |
INACTIVE | você | Pausado por você; nada é entregue, e os fatos que acontecem durante a pausa não geram evento para este webhook |
SUSPENDED | a IORQ | Trê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:
- Três dias sem
2xx. Uma entrega falha efailing_sincetem três dias ou mais. Independe demax_attempts, e um4xxcontí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. - Agenda esgotada com o endpoint indisponível. Um evento esgota todas as tentativas, o webhook não teve nenhum
2xxdesde antes da primeira delas (com pelo menos 24 horas de falha) e a última resposta indica endpoint indisponível: sem resposta,3xx,5xx,408ou429. Com a agenda padrão, cerca de 70 horas.suspension_reason:agenda de reentrega esgotada sem entrega bem-sucedida (71 h). 4xxnão abre a suspensão antecipada, salvo408e429. É 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 com4xxe aceita as outras entregas volta afailing_since: nulla 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 quandostatussair deACTIVEoufailing_sincedeixar de sernull. O campo abre na primeira falha, então o alerta chega dias antes da suspensão. - Conte, do seu lado, as respostas não-
2xxque 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_versiondiz qual:nullé a mensagem v1 que o produtor publicou ({"event", "data"}, sem oiorq_fund_ide oevent_timestampque 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.teste os fatos que o produtor já publica em v2 aparecem comschema_version: "2026-10-01"e o envelope empayload, 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:
| Campo | O que é |
|---|---|
response_status | O 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_final | true 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 |
payload | O 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_at | O 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:
| Campo | Fuso |
|---|---|
Event.created_at, Attempt.created_at, failing_since, suspended_at, previous_secret_expires_at | UTC, ISO-8601 com offset (2026-09-20T03:00:00.123456+00:00) |
Webhook.created_at, Webhook.updated_at | Horá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/replay | Exigem 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.
| Rota | Permissão |
|---|---|
POST /api/webhook | wsm_create_webhook |
GET /api/webhook, GET /api/webhook/{webhook_id} | wsm_read_webhook |
PATCH /api/webhook/{webhook_id}, POST /api/webhook/{webhook_id}/secret/rotate | wsm_update_webhook |
POST /api/webhook/{webhook_id}/test | wsm_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/replay | wsm_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
Updated 9 days ago
