> ## Documentation Index
> Fetch the complete documentation index at: https://userepass.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receba eventos do Repass em tempo real via webhooks.

Webhooks entregam os [Eventos](/docs/conceitos/event-store) do Repass aos seus servidores por HTTP, em tempo real. Você assina um subconjunto da taxonomia de eventos por endpoint, e o Repass faz `POST` no seu endpoint sempre que um desses eventos acontece: afiliado aprovado, conversão registrada, comissão paga, payout concluído, e assim por diante.

Cada evento assinado vira uma entrega assinada com HMAC, com retry exponencial e dead letter em caso de falha. O corpo entregue tem exatamente o mesmo shape do `GET /events/{id}`, então tudo o que você recebe via webhook também é consultável pela API.

<Note>
  Webhooks são o canal de **push**; o `GET /events` é o canal de **pull**. Use os dois em conjunto: o webhook avisa em tempo real e a API permite recuperar qualquer evento perdido. A entrega é **at-least-once**: deduplique pelo `id` do evento (veja [Assinatura](/docs/webhooks/assinatura)).
</Note>

## Criar um endpoint

Crie um endpoint com `POST /webhook-endpoints`, informando a `url` de destino e a lista de tipos de evento que quer receber (`subscribedEvents`). A lista é validada contra a taxonomia da plataforma: um tipo desconhecido é rejeitado com `400 parameter_invalid`.

<ParamField body="url" type="string" required>
  URL HTTPS que receberá as entregas (`POST`).
</ParamField>

<ParamField body="subscribedEvents" type="string[]" required>
  Tipos de evento assinados. No mínimo 1 elemento, todos validados contra o [catálogo de eventos](/docs/webhooks/catalogo-de-eventos). O tipo sintético `webhook.test` **não** pode ser assinado.
</ParamField>

<ParamField body="description" type="string">
  Texto livre opcional (máx. 500 caracteres).
</ParamField>

```bash theme={null}
curl https://api.userepass.com/webhook-endpoints \
  -H "Authorization: Bearer rstr_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: c0ffee00-0000-4000-8000-000000000001" \
  -d '{
    "url": "https://app.exemplo.com.br/hooks/repass",
    "description": "Producao",
    "subscribedEvents": [
      "conversion.created",
      "commission.approved",
      "commission.paid",
      "payout.completed"
    ]
  }'
```

A resposta `201` inclui o campo **`secret` em claro**: esta é a **única** vez que ele aparece em texto puro. Guarde-o de forma segura; em qualquer outra response ou evento o segredo vem apenas mascarado, no campo `maskedSecret` (`rwhs_***<últimos 4>`).

```json theme={null}
{
  "id": "whep_01HZX9QK7M3R8V4N2P6T0YBC5D",
  "url": "https://app.exemplo.com.br/hooks/repass",
  "description": "Producao",
  "subscribedEvents": [
    "conversion.created",
    "commission.approved",
    "commission.paid",
    "payout.completed"
  ],
  "status": "active",
  "disabledReason": null,
  "maskedSecret": "rwhs_***NyZXQ",
  "secret": "rwhs_dGhpc19pc19hX3JhbmRvbV9zZWNyZXQ",
  "previousSecretExpiresAt": null,
  "createdAt": "2026-06-13T12:00:00.000Z"
}
```

<Warning>
  O secret aparece em claro apenas na resposta do create e do `rotate-secret`. Se você o perder, rode `rotate-secret` para gerar um novo. Use o secret para validar a assinatura HMAC de cada entrega. Veja [Assinatura](/docs/webhooks/assinatura).
</Warning>

Um endpoint recém-criado **não recebe histórico**: ele só passa a receber eventos que acontecem a partir da sua criação. Para recuperar eventos anteriores, use o `GET /events`.

## Payload entregue

Cada entrega é um `POST` cujo corpo é o **envelope do evento**, o mesmo shape retornado por `GET /events/{id}`. A entrega também carrega os headers `Repass-Signature` (assinatura HMAC) e `content-type: application/json`.

<ResponseField name="id" type="string">
  ID do evento (`evt_<ULID>`). Use-o para **deduplicar** entregas repetidas.
</ResponseField>

<ResponseField name="organizationId" type="string">
  ID da organização dona do evento (`org_...`).
</ResponseField>

<ResponseField name="type" type="string">
  Tipo namespaced `recurso.ação` (ex.: `commission.paid`). Veja o [catálogo de eventos](/docs/webhooks/catalogo-de-eventos).
</ResponseField>

<ResponseField name="version" type="integer">
  Versão do schema do payload daquele tipo. Hoje todos os tipos estão em `version: 1`.
</ResponseField>

<ResponseField name="aggregateType" type="string">
  Agregado a que o evento se refere (ex.: `commission`).
</ResponseField>

<ResponseField name="aggregateId" type="string">
  ID do agregado (ex.: `comm_...`).
</ResponseField>

<ResponseField name="payload" type="object">
  Snapshot do fato: diff, entidade completa ou campos específicos, conforme o tipo. Dinheiro em centavos (inteiro), percentuais em basis points.
</ResponseField>

<ResponseField name="metadata" type="object">
  `{ actor: { type, id }, source? }`, onde `actor.type` é `user`, `api_key` ou `system`.
</ResponseField>

<ResponseField name="occurredAt" type="string">
  Quando o fato ocorreu (ISO-8601).
</ResponseField>

<ResponseField name="recordedAt" type="string">
  Quando o evento foi gravado no store (ISO-8601).
</ResponseField>

```json theme={null}
{
  "id": "evt_01HZXB2M9P4Q7R5S3T8V0WCD6E",
  "organizationId": "org_01HZX7VN4F2J8K0M3P5R7TBC2D",
  "type": "commission.paid",
  "version": 1,
  "aggregateType": "commission",
  "aggregateId": "comm_01HZXAYR2K8M4N6P0Q3T5VBC9D",
  "payload": {
    "affiliateId": "aff_01HZX8WP5J3K7M9N2Q4R6TBC0D",
    "amountCents": 12500,
    "payoutId": "pay_01HZXBQ7N4P2R6S8T0V3WCD5E"
  },
  "metadata": {
    "actor": { "type": "system", "id": "payout-cycle" }
  },
  "occurredAt": "2026-06-13T11:59:58.000Z",
  "recordedAt": "2026-06-13T11:59:58.412Z"
}
```

Responda com `2xx` para confirmar o recebimento. Qualquer outra resposta (ou timeout de 10s) é tratada como falha e dispara o retry. Veja [Retries e dead letter](/docs/webhooks/retries-e-dead-letter).

## Gerenciar endpoints

<Tabs>
  <Tab title="Listar">
    `GET /webhook-endpoints` lista os endpoints da organização, paginado por cursor ULID em ordem decrescente. Secrets sempre mascarados.

    ```bash theme={null}
    curl https://api.userepass.com/webhook-endpoints?limit=25 \
      -H "Authorization: Bearer rstr_..."
    ```
  </Tab>

  <Tab title="Detalhar">
    `GET /webhook-endpoints/{endpointId}` retorna um endpoint (secret mascarado).

    ```bash theme={null}
    curl https://api.userepass.com/webhook-endpoints/whep_01HZX9QK7M3R8V4N2P6T0YBC5D \
      -H "Authorization: Bearer rstr_..."
    ```
  </Tab>

  <Tab title="Atualizar">
    `PATCH /webhook-endpoints/{endpointId}` altera `url`, `description`, `subscribedEvents` ou `status`. Para desativar manualmente, envie `status: "disabled"`; para reativar, `status: "active"` (limpa o `disabledReason`).

    ```bash theme={null}
    curl -X PATCH https://api.userepass.com/webhook-endpoints/whep_01HZX9QK7M3R8V4N2P6T0YBC5D \
      -H "Authorization: Bearer rstr_..." \
      -H "Content-Type: application/json" \
      -d '{ "subscribedEvents": ["conversion.created", "conversion.refunded"] }'
    ```
  </Tab>

  <Tab title="Remover">
    `DELETE /webhook-endpoints/{endpointId}` faz **hard delete** e apaga o log de entregas junto (`204`).

    ```bash theme={null}
    curl -X DELETE https://api.userepass.com/webhook-endpoints/whep_01HZX9QK7M3R8V4N2P6T0YBC5D \
      -H "Authorization: Bearer rstr_..."
    ```
  </Tab>
</Tabs>

Todas as rotas são autenticadas (sessão ou API key `Authorization: Bearer rstr_...`) e escopadas pela sua organização. Um endpoint de outra organização retorna `404`, não `403`. Para a referência completa de campos e respostas, veja a aba Referência da API.

### Ciclo de vida do endpoint

```mermaid theme={null}
stateDiagram-v2
    [*] --> active: POST /webhook-endpoints
    active --> disabled: PATCH status=disabled (manual)
    active --> disabled: auto-disable >=95% falha / 72h (failure_rate)
    disabled --> active: PATCH status=active (limpa disabledReason)
    active --> [*]: DELETE (hard delete + log)
    disabled --> [*]: DELETE (hard delete + log)
```

* **active** (inicial): recebe eventos e tentativas de entrega.
* **disabled**: não recebe novos eventos; entregas em curso encerram sem retry e replay é recusado com `409 conflict`. O `disabledReason` registra a causa: `manual` (via `PATCH`) ou `failure_rate` (auto-desativação). Veja [Retries e dead letter](/docs/webhooks/retries-e-dead-letter) para a regra de auto-desativação.

## Rotacionar o secret

`POST /webhook-endpoints/{endpointId}/rotate-secret` gera um novo secret, retém o anterior e define uma **graça de 24h** (devolvida em `previousSecretExpiresAt`). Durante a graça, cada entrega é assinada com os **dois** secrets (dois `v1=`, o novo primeiro), para você migrar sem perder entregas. Após 24h, só o secret novo assina.

```bash theme={null}
curl -X POST https://api.userepass.com/webhook-endpoints/whep_01HZX9QK7M3R8V4N2P6T0YBC5D/rotate-secret \
  -H "Authorization: Bearer rstr_..." \
  -H "Idempotency-Key: c0ffee00-0000-4000-8000-000000000002"
```

A resposta é o endpoint completo (igual ao detalhe) acrescido do campo `secret` em claro. O secret atual fica mascarado em `maskedSecret`, e o fim da graça vem em `previousSecretExpiresAt`.

```json theme={null}
{
  "id": "whep_01HZX9QK7M3R8V4N2P6T0YBC5D",
  "url": "https://app.exemplo.com.br/hooks/repass",
  "status": "active",
  "disabledReason": null,
  "maskedSecret": "rwhs_***lcmU",
  "secret": "rwhs_bmV3X3JvdGF0ZWRfc2VjcmV0X2hlcmU",
  "previousSecretExpiresAt": "2026-06-14T12:00:00.000Z",
  "createdAt": "2026-06-13T12:00:00.000Z",
  "updatedAt": "2026-06-13T12:00:00.000Z"
}
```

```mermaid theme={null}
sequenceDiagram
    participant Op as Voce
    participant API as POST /rotate-secret
    participant EP as Seu endpoint
    Op->>API: POST .../rotate-secret
    API-->>Op: secret NOVO (em claro, unica vez)
    Note over API: retem o secret anterior<br/>previousSecretExpiresAt = +24h
    API->>EP: durante a graca, entregas assinadas com 2 secrets (2x v1=)
    Note over API,EP: apos 24h, so o secret novo assina
```

A resposta também devolve o secret novo em claro (única vez). Como validar os dois `v1=` no receptor: [Assinatura](/docs/webhooks/assinatura).

## Testar um endpoint

`POST /webhook-endpoints/{endpointId}/test` entrega uma mensagem sintética `webhook.test`, assinada como uma entrega normal, **na hora** (one-shot síncrono). Esse evento **não** é gravado no event store e a entrega tem `eventId: null`: sucesso vira `succeeded`, falha vai direto para `dead_letter` (sem entrar na fila de retry).

```bash theme={null}
curl -X POST https://api.userepass.com/webhook-endpoints/whep_01HZX9QK7M3R8V4N2P6T0YBC5D/test \
  -H "Authorization: Bearer rstr_..." \
  -H "Idempotency-Key: c0ffee00-0000-4000-8000-000000000003"
```

O corpo entregue tem o mesmo envelope dos eventos reais, com `type: "webhook.test"` e payload `{ "message": "Repass webhook test delivery" }`. Use-o para validar conectividade e a verificação de assinatura antes de assinar eventos de produção.

## Inspecionar e reenviar entregas

Cada tentativa de entrega fica registrada com request, response, status e latência por 30 dias. Dead letters ficam sem prazo, prontos para replay manual.

* `GET /webhook-endpoints/{endpointId}/deliveries`: log de entregas dos últimos 30 dias (cursor DESC). Filtro opcional `status` (`pending`, `attempting`, `succeeded`, `failed`, `dead_letter`).
* `GET /webhook-endpoints/{endpointId}/dead-letter`: entregas exauridas aguardando replay manual (sem janela de 30 dias).
* `POST /deliveries/{deliveryId}/replay`: reenfileira uma entrega referenciando a original (`202`); exige o endpoint **ativo** (senão `409 conflict`).

```bash theme={null}
curl -X POST https://api.userepass.com/deliveries/whdl_01HZXC4P8N2R6S0T3V5WCD7E9F/replay \
  -H "Authorization: Bearer rstr_..." \
  -H "Idempotency-Key: c0ffee00-0000-4000-8000-000000000004"
```

O replay **não** altera a entrega original: cria uma nova entrega `pending` que aponta para a original via `replayOfId`, processada com o cronograma de retry completo. Detalhes do cronograma, dead letter e auto-disable: [Retries e dead letter](/docs/webhooks/retries-e-dead-letter).

## Regras de negócio

<AccordionGroup>
  <Accordion title="Assinatura por endpoint e HMAC">
    Cada endpoint assina um subconjunto da taxonomia (validado contra o catálogo). A entrega leva o header `Repass-Signature: t=<unix>,v1=<hmac>`, com HMAC-SHA256 sobre `"{t}.{body}"`. O secret sai em claro apenas no create e no rotate-secret; em qualquer outro lugar vem mascarado. A rotação tem graça de 24h com assinatura dupla. Tolerância de replay sugerida: 5 minutos (validada pelo receptor). Veja [Assinatura](/docs/webhooks/assinatura).
  </Accordion>

  <Accordion title="Retry exponencial, dead letter e auto-disable">
    Cronograma de 7 tentativas: 0s, 30s, 5m, 30m, 2h, 12h, 24h. Após a exaustão, a entrega vai para dead letter (consultável e replayável sem prazo). Um endpoint com taxa de falha ≥ 95% em uma janela de 72h (amostra mínima de 20 tentativas) é desativado automaticamente com `disabledReason: failure_rate`. Veja [Retries e dead letter](/docs/webhooks/retries-e-dead-letter).
  </Accordion>

  <Accordion title="Log de entregas de 30 dias">
    O log de entregas (request, response, status, latência) fica disponível por 30 dias. Dead letters não expiram, para permitir replay manual a qualquer momento.
  </Accordion>
</AccordionGroup>

## Próximos passos

<CardGroup cols={2}>
  <Card title="Assinatura" icon="signature" href="/docs/webhooks/assinatura">
    Como validar o header `Repass-Signature` e deduplicar entregas.
  </Card>

  <Card title="Retries e dead letter" icon="rotate" href="/docs/webhooks/retries-e-dead-letter">
    Cronograma de retry, dead letter, replay e auto-disable.
  </Card>

  <Card title="Catálogo de eventos" icon="list" href="/docs/webhooks/catalogo-de-eventos">
    Todos os tipos de evento que você pode assinar.
  </Card>

  <Card title="Eventos" icon="database" href="/docs/conceitos/event-store">
    O histórico de eventos da sua organização, consultável via API.
  </Card>
</CardGroup>
