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

# Webhooks

> Verifique a assinatura de webhooks do Stripe, Shopify e Meta em uma linha: ctx.verifyStripe, verifyShopify, verifyMeta e verifyHmac.

Quando sua função recebe webhooks de um serviço externo, qualquer pessoa que conheça a URL pode enviar dados falsos. A verificação de assinatura confirma que o evento veio realmente do serviço.

`ctx.verify*` faz essa verificação em uma linha: lê o header de assinatura, resolve o secret dos seus [secrets](/pt/guias/funcoes/secrets) e lança erro se não corresponder.

## Stripe

```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";

export default define({
  name: "stripe-webhook",
  description: "Recebe eventos do Stripe",
  input: z.object({}).passthrough(),
  config: {
    public: true,
    path: "/webhooks/stripe",
    methods: ["POST"],
    mcp: false,
  },
  async handler(event, ctx, request) {
    await ctx.verifyStripe(request);

    ctx.log("Evento verificado", { tipo: event.type });

    if (event.type === "payment_intent.succeeded") {
      await ctx.jelou.send({
        type: "text",
        to: event.data.object.metadata.telefone,
        text: "Recebemos seu pagamento!",
      });
    }

    return { received: true };
  },
});
```

Configure o secret uma única vez:

```bash theme={null}
jelou functions secrets set stripe-webhook STRIPE_WEBHOOK_SECRET=whsec_...
```

<Note>
  O `event` que seu handler recebe já é o corpo validado com Zod. A plataforma consumiu o corpo da requisição para validá-lo, então chamar `request.json()` dentro do handler lança erro — use o primeiro parâmetro.
</Note>

## Provedores suportados

| Verificador                     | Header                  | Secret                                     |
| ------------------------------- | ----------------------- | ------------------------------------------ |
| `ctx.verifyStripe(request)`     | `stripe-signature`      | `STRIPE_WEBHOOK_SECRET`                    |
| `ctx.verifyShopify(request)`    | `x-shopify-hmac-sha256` | `SHOPIFY_WEBHOOK_SECRET`                   |
| `ctx.verifyMeta(request)`       | `x-hub-signature-256`   | `META_WEBHOOK_SECRET` ou `META_APP_SECRET` |
| `ctx.verifyHmac(request, opts)` | O que você indicar      | `opts.secretEnv` ou `opts.secret`          |

O Stripe também rejeita eventos com mais de 5 minutos, o que impede que alguém reenvie um evento antigo capturado.

## Outros serviços

Para Twilio, GitHub, Slack ou qualquer serviço com assinatura HMAC-SHA256, use `ctx.verifyHmac`. O header é obrigatório porque não existe valor padrão:

```typescript theme={null}
await ctx.verifyHmac(request, {
  secretEnv: "WEBHOOK_SECRET",
  header: "x-signature",
});
```

## Rotacionar secrets

Durante uma rotação, aceite o secret antigo e o novo ao mesmo tempo separando-os por vírgula:

```bash theme={null}
jelou functions secrets set meu-webhook STRIPE_WEBHOOK_SECRET=whsec_novo,whsec_antigo
```

Quando confirmar que o provedor já usa o novo, volte a deixar apenas um.

## Tratar a falha

Os verificadores lançam `WebhookVerificationError` com um código que indica o que aconteceu:

```typescript theme={null}
import { define, WebhookVerificationError, z } from "@jelou/functions";

async handler(event, ctx, request) {
  try {
    await ctx.verifyStripe(request);
  } catch (err) {
    if (err instanceof WebhookVerificationError) {
      ctx.log("Assinatura rejeitada", { code: err.code, provider: err.provider });
      return new Response(null, { status: 401 });
    }
    throw err;
  }

  return { received: true };
}
```

| Código              | Significado                                  |
| ------------------- | -------------------------------------------- |
| `missing_signature` | A requisição chegou sem header de assinatura |
| `invalid_signature` | A assinatura não corresponde ao corpo        |
| `expired_timestamp` | O evento é muito antigo (Stripe)             |
| `missing_secret`    | Você não configurou o secret desse provedor  |

<Tip>
  Se você não capturar o erro, a função responde com erro e o provedor fará retentativa do webhook. Para Stripe e Shopify isso costuma ser correto apenas quando a falha é temporária; diante de uma assinatura inválida é melhor responder `401` e não repetir.
</Tip>

## Funções públicas

Webhooks precisam de `config.public: true` para que o serviço externo possa chamá-los sem credenciais da Jelou. É justamente por isso que a verificação de assinatura é obrigatória: é o único controle de acesso que resta.

```typescript theme={null}
config: {
  public: true,      // o provedor não tem API key da Jelou
  methods: ["POST"], // webhooks são sempre POST
  mcp: false,        // não faz sentido como ferramenta de IA
}
```

Veja [funções públicas](/pt/guias/funcoes/public).

## Testes

Nos testes, `createMockContext()` deixa cada verificador lançando `missing_secret`. Para exercitar o resto do handler, desative a verificação:

```typescript theme={null}
import { createMockContext, createMockWebhookVerifiers } from "@jelou/functions/testing";

const verifiers = createMockWebhookVerifiers({ stripe: true });

const ctx = createMockContext({
  verifyStripe: verifiers.stripe,
});

// Depois de executar o handler você pode revisar as chamadas registradas
verifiers.calls; // [{ method: "stripe", ... }]
```

Veja o [guia de testes](/pt/guias/funcoes/testing).

<CardGroup cols={2}>
  <Card title="Funções públicas" icon="globe" href="/pt/guias/funcoes/public">
    Receber requisições sem credenciais da Jelou.
  </Card>

  <Card title="Secrets" icon="key" href="/pt/guias/funcoes/secrets">
    Guardar os secrets de cada provedor.
  </Card>

  <Card title="Execuções diferidas" icon="clock" href="/pt/guias/funcoes/diferidas">
    Agendar um follow-up ao receber o webhook.
  </Card>

  <Card title="Receptor de webhooks" icon="webhook" href="/pt/guias/funcoes/ejemplo-webhook">
    Exemplo completo pronto para copiar.
  </Card>
</CardGroup>
