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

# Consultas assíncronas e webhooks

> Três consultas não respondem na mesma requisição, porque o fornecedor pode levar minutos. Elas devolvem um protocolo para você acompanhar.

| Consulta | Como receber | Prazo e retenção |
| - | - | - |
| **Rastreamento veicular** | Webhook assinado ou `GET` do protocolo | Até 10 min · resultado guardado por 30 dias |
| **Buscador processual** | Webhook assinado ou `GET` do protocolo; processos em rota paginada | Até 30 min · 30 dias · até 1.000 processos |
| **JusScore** | Polling a cada \~15 s em `GET /api/v1/jus-score/{id_consulta}` | Reaproveita o resultado do mesmo documento por 30 dias |

> [!NOTE]
>
> O JusScore ainda não tem webhook na v1: enviar `url_retorno` responde `400`. Acompanhe pelo `GET`.

As duas primeiras usam o mesmo contrato de protocolo, descrito abaixo.

## Disparo

```bash cURL theme={"dark"}
curl -X POST https://api.jusfy.dev/calculators/api/v1/consultas/rastreamento-veicular \
  -H "x-api-key: $JUSFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"document":"ABC1D23","url_retorno":"https://seu-dominio.com/webhooks/jusfy"}'
```

```json 202 Accepted theme={"dark"}
{
  "statusCode": 202,
  "message": "Consulta assíncrona aceita",
  "result": {
    "id": "aqry_1f0b…",
    "status": "pending",
    "expiresAt": "2026-09-02T18:10:00.000Z",
    "statusUrl": "/api/v1/consultas/rastreamento-veicular/aqry_1f0b…",
    "signingSecret": "whsec_…"
  }
}
```

`document` é a placa (rastreamento) ou o CPF/CNPJ (buscador), com ou sem máscara. `url_retorno` é
opcional; quando informado, deve ser `https`, num domínio público na porta 443, e não pode ser um
endereço IP.

> [!WARNING]
>
> O `signingSecret` é **por consulta** e aparece uma única vez, neste `202`. Guarde-o para verificar
> a assinatura do webhook.

## Acompanhando pelo protocolo

`GET /api/v1/consultas/{consulta}/{id}` responde `200` em todos os estados — inclusive quando a
consulta falhou, porque o protocolo existe e o desfecho vem em `status`:

| `status` | Significado | Campos |
| - | - | - |
| `pending` | Ainda processando | `attempts` |
| `done` | Concluída | `data` com o resultado |
| `error` | O fornecedor encerrou sem resultado | `error` |
| `expired` | Não concluiu até `expiresAt` | `error` |

Consultar o protocolo é gratuito. Passados 30 dias, a consulta segue `done`, mas `data` vem `null`.

> [!NOTE]
>
> No **buscador processual** o resultado pode ter megabytes: o webhook leva os processos quando cabem
> em 64 KiB (`dataIncluded: true`) e só o resumo acima disso (`dataIncluded: false`, sem a chave
> `lawsuits`). A lista completa vem sempre por
> `GET /api/v1/consultas/buscador-processual/{id}/processos`, paginado. Com `truncated: true`, os
> processos guardados são uma amostra — a ordem do fornecedor não é cronológica.

## Recebendo o webhook

Quando a consulta termina, enviamos um `POST` ao seu `url_retorno`:

```json Corpo do webhook theme={"dark"}
{
  "id": "aqry_1f0b…",
  "event": "consulta.concluida",
  "consulta": "rastreamento-veicular",
  "status": "done",
  "createdAt": "2026-09-02T18:00:00.000Z",
  "completedAt": "2026-09-02T18:03:12.000Z",
  "attempt": 1,
  "data": {}
}
```

`event` é `consulta.concluida`, `consulta.falhou` ou `consulta.expirada`; em `error` e `expired`,
o corpo traz `error` no lugar de `data`.

| Header | Uso |
| - | - |
| `X-Jusfy-Signature` | `v1=<hmac-sha256 em hex>` |
| `X-Jusfy-Timestamp` | Segundos desde a época; entra na assinatura |
| `X-Jusfy-Async-Query-Id` | O mesmo `id` do protocolo |
| `X-Jusfy-Delivery` | Único **por tentativa** — torna a duplicata visível |
| `X-Jusfy-Event` | O mesmo valor do campo `event` |

**A entrega é at-least-once**: deduplique pelo `id`. Responda `2xx` para confirmar; qualquer `4xx`
(exceto `408` e `429`) encerra as tentativas na hora, e redirects não são seguidos. São até 6
tentativas ao longo de \~7h30; esgotadas, o resultado continua disponível pelo protocolo.

### Verificando a assinatura

Assine `${timestamp}.${corpo}` com o **corpo exato** que recebeu, sem reserializar — a ordem das
chaves mudaria e a assinatura não bateria.

```js Node.js theme={"dark"}
const crypto = require('crypto');

function isValid(rawBody, headers, signingSecret) {
  const timestamp = headers['x-jusfy-timestamp'];

  // Rejeita replay de payloads antigos.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected =
    'v1=' +
    crypto
      .createHmac('sha256', signingSecret)
      .update(`${timestamp}.${rawBody}`)
      .digest('hex');

  const received = Buffer.from(headers['x-jusfy-signature']);
  const computed = Buffer.from(expected);
  return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}
```

## Cobrança

A consulta é **cobrada no disparo**, não na entrega — inclusive se depois expirar. Consultar o
protocolo e receber o webhook não são cobrados. Não há deduplicação: dois disparos para o mesmo
documento são duas consultas cobradas. Se o `POST` falhar por erro de rede **sem resposta**, consulte
antes de repetir.

Há um limite de consultas assíncronas em processamento por conta; acima dele, o disparo responde
`429` com `code: too_many_pending_queries` e `Retry-After`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.