> 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/construa/como-fazer/disparar-um-evento-pela-api.md).

# Disparar um evento pela API

Como acordar uma function que está parada numa espera por evento, a partir de um webhook, de uma tela ou de um job.

O disparo é um `POST` na API do skail; esta página mostra o padrão completo dos dois lados.

## Pré-requisitos

O cliente HTTP do skail configurado como em [Como disparar uma função pela API HTTP](/construa/como-fazer/disparar-uma-funcao-pela-api.md), com URL base, `Authorization`, `skail-key` e `Skail-namespace`.

## 1. Defina o contrato do evento em um lugar só

{% tabs %}
{% tab title="C#" %}

```csharp
public static class Eventos
{
    public const string PagamentoConfirmado = "PAGAMENTO_CONFIRMADO";
}

public record ConfirmacaoPagamento(bool Aprovado, string TransacaoId, string? MotivoRecusa);
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
export const Eventos = {
  PagamentoConfirmado: "PAGAMENTO_CONFIRMADO",
} as const;

export interface ConfirmacaoPagamento {
  Aprovado: boolean;
  TransacaoId: string;
  MotivoRecusa: string | null;
}
```

{% endtab %}
{% endtabs %}

O nome fica em uma constante e o payload em um tipo simples. Os dois lados (a function e quem dispara) referenciam os mesmos tipos; se estão em projetos diferentes, um pacote compartilhado.

## 2. Espere na function

{% tabs %}
{% tab title="C#" %}

```csharp
[SkailFunction]
public async SkailTask ProcessarFatura(Guid faturaId)
{
    await EnviarParaCobranca(faturaId);                                   // command: chama o gateway

    var confirmacao = SkailTask.WaitForEvent<ConfirmacaoPagamento>(Eventos.PagamentoConfirmado, faturaId.ToString());
    var prazo       = SkailTask.Delay(TimeSpan.FromHours(48));

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

    var resultado = await confirmacao;
    if (resultado.Aprovado) await LiberarPedido(faturaId);
    else                    await RegistrarRecusa(faturaId, resultado.MotivoRecusa);
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

export const Eventos = {
  PagamentoConfirmado: "PAGAMENTO_CONFIRMADO",
} as const;

export interface ConfirmacaoPagamento {
  Aprovado: boolean;
  TransacaoId: string;
  MotivoRecusa: string | null;
}

export class FaturaService {
  @SkailFunction()
  async ProcessarFatura(faturaId: string): Promise<void> {
    await this.EnviarParaCobranca(faturaId);                              // command: chama o gateway

    const confirmacao = Skail.waitForEvent<ConfirmacaoPagamento>(Eventos.PagamentoConfirmado, faturaId);
    const prazo       = Skail.delayMinutes(48 * 60);

    const resultado = await Skail.whenAny(confirmacao, prazo);
    if (resultado === undefined) {
      await this.CancelarPorExpiracao(faturaId);
      return;
    }

    if (resultado.Aprovado) await this.LiberarPedido(faturaId);
    else                    await this.RegistrarRecusa(faturaId, resultado.MotivoRecusa);
  }

  @SkailCommand() async EnviarParaCobranca(faturaId: string): Promise<void> {}
  @SkailCommand() async CancelarPorExpiracao(faturaId: string): Promise<void> {}
  @SkailCommand() async LiberarPedido(faturaId: string): Promise<void> {}
  @SkailCommand() async RegistrarRecusa(faturaId: string, motivo: string | null): Promise<void> {}
}
```

{% endtab %}
{% endtabs %}

O instance id é o `faturaId`, o mesmo id que o gateway vai devolver no webhook. A espera tem prazo de 48 horas.

## 3. Dispare a partir do webhook

O gateway de pagamento chama o seu endpoint quando o pagamento é processado. O endpoint faz o fire:

{% tabs %}
{% tab title="C#" %}

```csharp
using System.Net.Http.Json;
using System.Text.Json;

[ApiController]
[Route("webhooks/gateway")]
public class GatewayWebhookController : ControllerBase
{
    private readonly HttpClient _skail;

    public GatewayWebhookController(IHttpClientFactory factory) => _skail = factory.CreateClient("skail");

    [HttpPost("pagamento")]
    public async Task<IActionResult> Pagamento([FromBody] NotificacaoGateway notificacao, CancellationToken ct)
    {
        // valide a assinatura do webhook antes de qualquer coisa

        var confirmacao = new ConfirmacaoPagamento(
            Aprovado: notificacao.Status == "approved",
            TransacaoId: notificacao.TransactionId,
            MotivoRecusa: notificacao.DeclineReason);

        var resposta = await _skail.PostAsJsonAsync(
            $"/api/v1/fire/{Eventos.PagamentoConfirmado}/{notificacao.InvoiceId}",
            confirmacao, JsonSerializerOptions.Default, ct);

        resposta.EnsureSuccessStatusCode();
        return Ok();
    }
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
export const Eventos = {
  PagamentoConfirmado: "PAGAMENTO_CONFIRMADO",
} as const;

export interface ConfirmacaoPagamento {
  Aprovado: boolean;
  TransacaoId: string;
  MotivoRecusa: string | null;
}

export interface NotificacaoGateway {
  status: string;
  transactionId: string;
  declineReason: string | null;
  invoiceId: string;
}

export interface SkailHttp {
  post(path: string, body: unknown, signal?: AbortSignal): Promise<Response>;
}

export class GatewayWebhookController {
  constructor(private readonly skail: SkailHttp) {}

  async Pagamento(notificacao: NotificacaoGateway, signal?: AbortSignal): Promise<{ status: 200 }> {
    // valide a assinatura do webhook antes de qualquer coisa

    const confirmacao: ConfirmacaoPagamento = {
      Aprovado: notificacao.status === "approved",
      TransacaoId: notificacao.transactionId,
      MotivoRecusa: notificacao.declineReason,
    };

    const resposta = await this.skail.post(
      `/api/v1/fire/${Eventos.PagamentoConfirmado}/${notificacao.invoiceId}`,
      confirmacao,
      signal,
    );

    if (!resposta.ok) throw new Error(`fire falhou: ${resposta.status}`);
    return { status: 200 };
  }
}
```

{% endtab %}
{% endtabs %}

O caminho leva o nome do evento (a constante) e o instance id (o id da fatura que veio no webhook). O corpo é o payload do passo 1, que a function recebe como resultado da espera pelo evento.

O endpoint não grava nada. O que precisa ser persistido a partir da confirmação (a transação, o status da fatura) é gravado pela function, em um command, depois que o evento chega. Gravar no endpoint e só depois disparar deixa dois registros que podem divergir: se o disparo falhar, o banco diz pago e a function continua esperando. Dentro da execução, a gravação tem o retry e a retomada do skail.

{% hint style="success" %}
O que você deve ver: no Monitor, a execução sai de aguardando evento, o evento recebido aparece na linha do tempo, e a function continua em `LiberarPedido` ou `RegistrarRecusa`.
{% endhint %}

## Se o webhook chega antes

O gateway pode responder em segundos, enquanto a function ainda está em `EnviarParaCobranca`. O evento é entregue assim que a execução alcançar a espera. Desde que o fire venha depois do trigger, você não precisa tratar a ordem; ver [Garantias de execução](/referencia/garantias/garantias-de-execucao.md).

## Se o webhook falhar ao chamar o skail

Se o `POST` para o skail falhar (rede, 5xx), devolva erro ao gateway para que ele reenvie o webhook, ou retente com backoff. Gateways sérios reenviam; se o seu não reenvia, o retry precisa ser seu. Um fire repetido não é descartado: ele fica guardado e atende a próxima espera com o mesmo nome e id. Se nenhuma outra espera vai usar esse nome com o mesmo id, ele fica sem efeito; se a function tem várias esperas com o mesmo nome e id, cada fire atende uma, então use um id diferente para cada espera.

## Disparar de uma tela

Quando quem dispara é uma pessoa (aprovação, revisão), o padrão é o mesmo: o BFF recebe o clique e faz o fire com o id da solicitação. Ver [Human-in-the-loop](/construa/como-fazer/padroes-e-antipadroes/human-in-the-loop.md).

## Disparar de dentro de uma execução durável

Se uma function precisa acordar outra execução, faça o POST do fire em um command, como qualquer integração externa. A doc cobre o disparo pela API HTTP.

## Erros comuns

| Sintoma                                                                            | Causa                                                                                                                   | Correção                                                                                                                                                      |
| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Execução continua aguardando após o fire                                           | Nome ou instance id diferentes entre a espera e o fire (caixa, espaço, id com e sem hifens)                             | Constantes compartilhadas; o mesmo id na espera e no fire                                                                                                     |
| 2xx no fire, nada acontece                                                         | URL base, chave e namespace de outro environment (a chamada foi aceita lá)                                              | Confira se `SKAIL_BASE_URL`, chave e namespace são do environment onde a execução está esperando                                                              |
| Function recebe payload com campos vazios ou com valor padrão (`false`, `0`)       | Nomes das propriedades do JSON não batem com o tipo do payload, inclusive na caixa (`aprovado` não preenche `Aprovado`) | Use o mesmo tipo na espera e no fire; em C#, passe `JsonSerializerOptions.Default` no `PostAsJsonAsync`                                                       |
| Function C# falha ao retomar depois do fire, sem linha de erro no log da aplicação | Enum do tipo do payload mandado como texto (o nome do membro em vez do número)                                          | Enum como número no JSON, ou campo `string` no tipo. Ver [Passar dados entre função e commands](/construa/como-fazer/passar-dados-entre-funcao-e-commands.md) |
| 400 com `missing_headers` no fire                                                  | Falta um dos três headers: `Authorization`, `skail-key` ou `Skail-namespace`                                            | Confira o cliente configurado em [Como disparar uma função pela API HTTP](/construa/como-fazer/disparar-uma-funcao-pela-api.md)                               |
| 400 com `invalid_body` no fire                                                     | Corpo inválido                                                                                                          | Mande o payload como um objeto JSON                                                                                                                           |

## Próximos passos

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><i class="fa-paper-plane">:paper-plane:</i></td><td><strong>POST /api/v1/fire</strong></td><td>É a referência da rota.</td><td><a href="/referencia/api-http/post-fire.md">POST fire</a></td></tr><tr><td><i class="fa-hourglass-half">:hourglass-half:</i></td><td><strong>Como esperar com timeout</strong></td><td>Detalha o prazo.</td><td><a href="/construa/como-fazer/esperar-um-evento-com-prazo.md">Esperar um evento com prazo</a></td></tr><tr><td><i class="fa-tower-broadcast">:tower-broadcast:</i></td><td><strong>Eventos externos e correlação</strong></td><td>Explica o mecanismo.</td><td><a href="/construa/conceitos/eventos-externos-e-correlacao.md">Eventos externos e correlação</a></td></tr></tbody></table>
