# Webhooks

> **In English.** Register one or more URLs per account and receive a JSON `POST` for each event:
> document sent for signature, signer added, signer signed, all signatures finished, document status
> changed, document removed, form filled, signatures expired, signatures canceled. Any 2xx within
> 20 seconds confirms delivery; failures are retried up to 5 times. New events may be added, so route
> by the `event` you know and discard the rest with a 2xx. There is no signature header and no
> delivery id: validate the origin with `accountId` (and a secret in your URL) and treat repeats by
> the content of `entity`. Text in Brazilian Portuguese.

Webhooks são a forma recomendada de acompanhar documentos: em vez de consultar a situação
repetidamente, sua URL recebe um `POST` a cada evento da conta.

## Cadastro

Cadastre a URL com `partners_v1_webhooks_create` (exige a feature `integrations`):

```bash
curl -X POST -H "Authorization: SUA_CHAVE" -H "Content-Type: application/json" \
  https://api.letssign.com.br/partners/v1/accounts/0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f/webhooks \
  -d '{ "uri": "https://integracao.exemplo.com.br/letssign/webhook" }'
```

```json
{
  "id": "0199143e-4a5b-7c6d-9e7f-8a9b0c1d2e3f",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "uri": "https://integracao.exemplo.com.br/letssign/webhook",
  "available": true,
  "createdAt": "2026-08-20T14:05:00Z"
}
```

- A URL precisa começar com `http://` ou `https://`, ter o host em minúsculas e um TLD de 2 a 5
  letras. A mesma URL não pode ser cadastrada duas vezes na conta (`400`).
- No cadastro, um evento **`Test`** é enviado de imediato. Sua URL deve responder `200` ou `202`
  em até 20 segundos; o resultado fica em `available`. A URL é gravada mesmo quando o teste falha,
  e o `Test` não é reenviado depois.
- Cada URL cadastrada recebe **todos** os eventos da conta; não há filtro por evento. Para receber
  em mais de um sistema, cadastre mais de uma URL.
- Liste com `partners_v1_webhooks_list` e remova com `partners_v1_webhooks_delete`. Depois da
  remoção, eventos novos deixam de ser enviados àquela URL.

## O envelope

Cada entrega é um `POST` com `Content-Type: application/json; charset=utf-8` e este corpo:

| Campo | Tipo | O que traz |
|---|---|---|
| `event` | string | Nome do evento, que define o formato de `entity`. |
| `accountId` | UUID | Conta à qual o evento pertence. Use para validar a origem e rotear. |
| `occurredAt` | data e hora (UTC) | Instante **desta tentativa de envio**, não do evento. Muda a cada retentativa. |
| `entity` | objeto | Os dados do evento. O schema de cada um está no objeto `webhooks` do documento OpenAPI e na seção de payloads abaixo. |

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "DocumentSignatureFinished",
  "entity": { "...": "depende do evento" }
}
```

## Os eventos

| Evento | Quando | O que fazer |
|---|---|---|
| `Test` | Uma vez, no cadastro da URL. `entity.test` é sempre `true`. | Responder 2xx. |
| `DocumentSentToSignature` | Um documento foi enviado para assinatura: na criação por `partners_v1_document_signatures_create_from_file`, em `partners_v1_documents_request_signatures` ou pelo aplicativo. Com envio agendado, sai no momento da criação. | Guardar `entity.signers[].id`: é o `signatureId` para editar, remover e reenviar. |
| `SignerAddedToDocument` | Um signatário foi adicionado a um documento já em assinatura. | Guardar `entity.signer.id`. |
| `DocumentSignatureMember` | Um signatário assinou. Chega uma vez por signatário, com papel, e-mail, nome informado e `signedAt`. | Atualizar o andamento. Quando é o último, também chega `DocumentSignatureFinished`. |
| `DocumentSignatureFinished` | O último signatário assinou: `entity.signatureStatus` é `Signed`. | Baixar o PDF assinado (`partners_v1_documents_download_signed`) ou, com certificado digital, `partners_v1_documents_download_digital_certificate`. |
| `DocumentStatusChanged` | O status do documento mudou: `Finished` (conteúdo pronto, em documentos gerados no editor ou por formulário) ou `NewVersionBase` (o documento virou base de uma nova versão, em `entity.newVersionId`). | Acompanhar a nova versão, se houver. |
| `DocumentRemoved` | O documento foi excluído, pela API ou pelo aplicativo. Ao excluir um envelope, sai uma vez, com o id do envelope. | Encerrar o acompanhamento; operações sobre o documento passam a responder `400`. |
| `FormFilled` | Um formulário criado por `partners_v1_forms_create` foi preenchido por completo e o documento foi gerado. | Solicitar as assinaturas com `partners_v1_documents_request_signatures`. |
| `DocumentSignaturesExpired` | O prazo de assinatura venceu e as assinaturas foram canceladas pelo processo automático diário. `entity.deadlineForSignature` é o prazo que venceu. Sai uma vez por envelope, e `entity.documents` traz só os documentos atingidos. | Guardar `documents[].pendingSigners`: quem não assinou deixa de existir depois do cancelamento. Reenviar com `partners_v1_documents_request_signatures` e um prazo novo, se for o caso. |
| `DocumentSignaturesCanceled` | As assinaturas foram canceladas a pedido, pela API ou pelo aplicativo. Numa exclusão só chega se o documento estava aguardando assinaturas, e parte das exclusões feitas no aplicativo não o envia. Mesmo formato do evento acima, sem o prazo. | Encerrar o acompanhamento, ou reabrir com `partners_v1_documents_request_signatures`. |

## Contrato de entrega

- **Confirmação.** Qualquer status 2xx confirma a entrega; `200` ou `202` são os recomendados. O
  corpo da sua resposta é ignorado.
- **Tempo limite.** 20 segundos. Outro status, tempo esgotado ou erro de rede contam como falha.
- **Retentativas.** A entrega é repetida em seguida, até 5 tentativas, sem espera progressiva.
  Depois disso o evento não é reenviado.
- **Sem assinatura.** Não há cabeçalho de autenticação nem assinatura HMAC no payload. Valide a
  origem pelo `accountId` (ele precisa ser uma conta sua) e, se quiser uma camada a mais, inclua um
  segredo na própria URL cadastrada (`https://.../webhook?token=...`) e confira-o a cada entrega.
- **Sem identificador de entrega.** Trate repetições pelo conteúdo de `entity`: por exemplo,
  `event` + `entity.documentId` + `entity.signer.id` + `signedAt` identificam uma assinatura.
- **Sem garantia de ordem**, mesmo entre eventos do mesmo documento. Para saber o estado atual,
  consulte `partners_v1_document_signatures_status`.

## Como tratar no seu lado

1. **Responda rápido.** Grave o payload numa fila e responda `202`; processe depois. Assim você fica
   longe dos 20 segundos mesmo em picos.
2. **Seja idempotente.** Guarde uma chave derivada do conteúdo e ignore o que já processou.
3. **Valide a origem.** Rejeite (`4xx`) payloads cujo `accountId` não seja seu ou cujo segredo na
   URL não bata.
4. **Reconcilie.** Como não há ordem garantida, ao receber `DocumentSignatureMember` confirme o
   estado com `partners_v1_document_signatures_status` antes de decisões irreversíveis.
5. **Registre.** Guarde o payload bruto e o instante de recebimento; sem identificador de entrega,
   é o seu registro que permite investigar.
6. **Monitore `available`.** Se o teste do cadastro falhou, corrija a URL e cadastre de novo (remova
   a anterior primeiro).
7. **Descarte o que você não trata.** Eventos novos podem ser criados. Faça o roteamento pelo `event`
   que você conhece, responda 2xx para os demais e descarte-os: um `4xx`/`5xx` num evento que você
   não trata só gera retentativa e ruído no seu registro.

## Payloads de exemplo

Os exemplos abaixo são os mesmos do objeto `webhooks` do documento OpenAPI, com o envelope
completo.

### `Test`

Enviado no cadastro de uma URL de webhook, para verificar a disponibilidade.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "Test",
  "entity": {
    "test": true
  }
}
```

### `DocumentSentToSignature`

Documento enviado para assinatura.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "DocumentSentToSignature",
  "entity": {
    "documentId": "01991440-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
    "sentAt": "2026-08-20T14:05:00Z",
    "signers": [
      {
        "id": "01991441-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
        "role": "Parte",
        "authenticationMethod": "Email",
        "email": "maria.silva@exemplo.com.br",
        "name": "Maria da Silva"
      },
      {
        "id": "01991441-9f8e-7d6c-8b5a-4c3d2e1f0a9b",
        "role": "Testemunha",
        "authenticationMethod": "Email",
        "email": "joao.souza@exemplo.com.br",
        "name": "João de Souza"
      }
    ]
  }
}
```

### `SignerAddedToDocument`

Novo signatário adicionado ao documento.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "SignerAddedToDocument",
  "entity": {
    "documentId": "01991440-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
    "signer": {
      "id": "01991441-9f8e-7d6c-8b5a-4c3d2e1f0a9b",
      "role": "Testemunha",
      "authenticationMethod": "Email",
      "email": "joao.souza@exemplo.com.br",
      "name": "João de Souza"
    }
  }
}
```

### `DocumentSignatureMember`

Signatário assinou o documento.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "DocumentSignatureMember",
  "entity": {
    "documentId": "01991440-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
    "role": "Parte",
    "email": "maria.silva@exemplo.com.br",
    "name": "Maria da Silva",
    "signedAt": "2026-08-21T10:12:45Z"
  }
}
```

### `DocumentSignatureFinished`

Todos signatários assinaram o documento.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "DocumentSignatureFinished",
  "entity": {
    "id": "01991440-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
    "status": "Finished",
    "signatureStatus": "Signed"
  }
}
```

### `DocumentStatusChanged`

Documento teve status alterado.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "DocumentStatusChanged",
  "entity": {
    "status": "NewVersionBase",
    "newVersionId": "0199144e-b8c9-7d0e-8f1a-2b3c4d5e6f7a",
    "id": "01991440-5a6b-7c8d-9e0f-1a2b3c4d5e6f"
  }
}
```

### `DocumentRemoved`

Documento foi removido.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "DocumentRemoved",
  "entity": {
    "id": "01991440-5a6b-7c8d-9e0f-1a2b3c4d5e6f"
  }
}
```

### `FormFilled`

Formulário foi preenchido.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "FormFilled",
  "entity": {
    "formId": "01991443-7d8e-7f9a-8b1c-2d3e4f5a6b7c",
    "documentId": "01991440-5a6b-7c8d-9e0f-1a2b3c4d5e6f"
  }
}
```

### `DocumentSignaturesExpired`

Prazo de assinatura do documento venceu.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "DocumentSignaturesExpired",
  "entity": {
    "deadlineForSignature": "2026-09-08T00:00:00Z",
    "id": "01991447-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
    "documents": [
      {
        "id": "01991440-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
        "pendingSigners": [
          {
            "id": "01991441-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
            "role": "Parte",
            "authenticationMethod": "Email",
            "email": "maria.silva@exemplo.com.br",
            "name": "Maria da Silva"
          },
          {
            "id": "01991441-9f8e-7d6c-8b5a-4c3d2e1f0a9b",
            "role": "Testemunha",
            "authenticationMethod": "Email",
            "email": "joao.souza@exemplo.com.br",
            "name": "João de Souza"
          }
        ]
      }
    ]
  }
}
```

### `DocumentSignaturesCanceled`

Assinaturas do documento foram canceladas.

```json
{
  "occurredAt": "2026-08-21T10:12:45Z",
  "accountId": "0199143a-7c2e-7b1a-9f2d-3c4b5a6d7e8f",
  "event": "DocumentSignaturesCanceled",
  "entity": {
    "id": "01991447-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
    "documents": [
      {
        "id": "01991440-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
        "pendingSigners": [
          {
            "id": "01991441-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
            "role": "Parte",
            "authenticationMethod": "Email",
            "email": "maria.silva@exemplo.com.br",
            "name": "Maria da Silva"
          },
          {
            "id": "01991441-9f8e-7d6c-8b5a-4c3d2e1f0a9b",
            "role": "Testemunha",
            "authenticationMethod": "Email",
            "email": "joao.souza@exemplo.com.br",
            "name": "João de Souza"
          }
        ]
      }
    ]
  }
}
```
