> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/construir/padroes-e-antipadroes/polling-de-sistema-externo.md).

# Polling de sistema externo

Consultar um sistema que não avisa quando termina: sem webhook, sem evento, só uma API de status. O padrão é um laço com um command que consulta, um `Delay` crescente entre consultas e um limite de tentativas, tudo durável.

## Quando usar

Emissão em órgão público que "está processando", relatório que o parceiro gera em minutos, transferência que o banco confirma depois, job em outro sistema. Se o sistema oferece webhook, prefira [esperar um evento](/construir/escrever-fluxos/como-disparar-um-evento-pela-api-http.md); polling é para quando não oferece, ou como plano B quando o webhook não chegou no prazo.

## O padrão

```csharp
public enum StatusEmissao { Processando, Autorizada, Rejeitada }
public record ConsultaEmissao(StatusEmissao Status, string? Protocolo, string? Motivo);

[SkailFunction]
public async SkailTask EmitirNotaNaPrefeitura(Guid notaId)
{
    var recibo = await EnviarLote(notaId);                          // command: inicia o processamento lá

    var intervalo = TimeSpan.FromSeconds(30);
    for (var tentativa = 1; tentativa <= 20; tentativa++)
    {
        await SkailTask.Delay(intervalo);

        var consulta = await ConsultarLote(recibo);                  // command: pergunta o status
        switch (consulta.Status)
        {
            case StatusEmissao.Autorizada:
                await RegistrarAutorizacao(notaId, consulta.Protocolo!);
                return;
            case StatusEmissao.Rejeitada:
                await RegistrarRejeicao(notaId, consulta.Motivo!);
                return;
        }

        intervalo = intervalo < TimeSpan.FromMinutes(10) ? intervalo * 2 : TimeSpan.FromMinutes(10);
    }

    await MarcarParaAnaliseManual(notaId, recibo);                   // esgotou: alguém precisa olhar
}

[SkailCommand(retryCount: 5)]
public async SkailTask<string> EnviarLote(Guid notaId) { /* HTTP; idempotente pelo notaId */ }

[SkailCommand(retryCount: 3)]
public async SkailTask<ConsultaEmissao> ConsultarLote(string recibo) { /* HTTP GET */ }
```

O que está garantido. Cada consulta é um command: se a API de status falhar, o runtime retenta a consulta (3 vezes) antes de a exceção chegar à function. Cada `Delay` hiberna a execução; entre consultas, custo zero. O contador `tentativa` e o `intervalo` são variáveis locais, reconstruídas no replay a partir dos mesmos passos, então o laço é determinístico. O limite de 20 tentativas com intervalo crescente até 10 minutos cobre cerca de duas horas e meia; ajuste ao sistema. Quando esgota, a decisão vai para uma pessoa, registrada como command para aparecer no Monitor.

## Backoff

O intervalo dobra a cada consulta até um teto. Isso poupa o sistema externo quando ele está lento e responde rápido quando ele é rápido. O cálculo é aritmética sobre variáveis locais: pode ficar na function. O que não pode é adicionar jitter com `Random` na function; se quiser jitter, gere-o no command de consulta e devolva junto, ou derive-o deterministicamente do `notaId`.

## Polling com prazo total

Em vez de contar tentativas, limite por tempo total combinando com uma espera:

```csharp
var prazoTotal = SkailTask.Delay(TimeSpan.FromHours(3));
var intervalo  = TimeSpan.FromSeconds(30);

while (true)
{
    var espera = SkailTask.Delay(intervalo);
    if (await SkailTask.WhenAny(espera, prazoTotal) == prazoTotal) { await MarcarParaAnaliseManual(notaId, recibo); return; }

    var consulta = await ConsultarLote(recibo);
    if (consulta.Status != StatusEmissao.Processando) { /* trata e return */ }

    intervalo = intervalo < TimeSpan.FromMinutes(10) ? intervalo * 2 : TimeSpan.FromMinutes(10);
}
```

## Webhook com polling de segurança

O parceiro tem webhook, mas às vezes ele não chega. Espere o evento com prazo; no timeout, consulte por polling antes de declarar falha:

```csharp
var confirmacao = SkailTask.WaitForEvent<Confirmacao>(Eventos.LoteProcessado, recibo);
var prazo       = SkailTask.Delay(TimeSpan.FromMinutes(15));

if (await SkailTask.WhenAny(confirmacao, prazo) == confirmacao)
{
    await Tratar(await confirmacao);
    return;
}

var consulta = await ConsultarLote(recibo);                         // o webhook não veio: pergunta
```

## O que não fazer

Consultar em laço dentro de um command com `Task.Delay` (ocupa a instância da aplicação por horas e um único command fica sem checkpoint). Polling sem limite (histórico infinito, ver [Antipadrões](/construir/padroes-e-antipadroes/antipadroes-o-que-nao-fazer-e-por-que.md)). Intervalo fixo de poucos segundos por horas (passos demais, pressão no sistema externo).

## Próximos passos

[Timeout e prazo](/construir/padroes-e-antipadroes/timeout-e-prazo.md) para as variações de prazo. [Tempo e agendamento](/aprender/fundamentos/tempo-e-agendamento.md) para o `Delay`. [Emissão de NFS-e](/aprender/exemplos-completos/emissao-de-nota-fiscal-de-servico.md) é um exemplo completo com processamento assíncrono no órgão.
