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

# Webhook de Pagamento

> Endpoint POST /api/webhook/payment do SSO. Chamado pelo app-front quando um pagamento é confirmado.

## POST /api/webhook/payment

Recebe notificações de pagamento do app-front, verificadas via assinatura HMAC SHA256.

**URL:** `https://sso.easygoal.com.br/api/webhook/payment`

**Headers:**

<ParamField header="Content-Type" type="string" required>
  `application/json`
</ParamField>

## Payload

```json theme={null}
{
  "event": "subscription.activated",
  "data": {
    "subscription_id": "550e8400-e29b-41d4-a716-446655440000",
    "product_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "product_name": "Club EasyGoal",
    "user_email": "usuario@email.com",
    "user_name": "João Silva",
    "amount": 9900,
    "frequency": "MONTHLY",
    "started_at": "2026-02-22T10:00:00.000Z",
    "is_test": false
  },
  "timestamp": "2026-02-22T10:00:00.000Z",
  "signature": "a3f8b2c1d4e5f6..."
}
```

**Campos do payload:**

<ParamField body="event" type="string" required>
  Tipo do evento. Atualmente: `subscription.activated`.
</ParamField>

<ParamField body="data.subscription_id" type="string" required>
  UUID da assinatura ativada.
</ParamField>

<ParamField body="data.product_name" type="string" required>
  Nome do produto SaaS.
</ParamField>

<ParamField body="data.user_email" type="string" required>
  Email do usuário que assinou.
</ParamField>

<ParamField body="data.amount" type="number" required>
  Valor da assinatura em centavos (ex: `9900` = R\$ 99,00).
</ParamField>

<ParamField body="data.is_test" type="boolean" required>
  Se `true`, o webhook é um teste. Não provisionar acesso real.
</ParamField>

<ParamField body="signature" type="string" required>
  Assinatura HMAC SHA256 do payload (sem o campo `signature`) codificada em hex.
</ParamField>

## Como o app-front assina o webhook

```ts theme={null}
// app-front → utilitário de webhook
import { createHmac } from 'crypto';

function signWebhook(payload: object, secret: string): string {
  return createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
}

// Enviando o webhook
const body = { event, data, timestamp };
const signature = signWebhook(body, process.env.SSO_WEBHOOK_SECRET!);

await fetch(process.env.SSO_WEBHOOK_URL!, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ ...body, signature }),
});
```

## Como o SSO verifica a assinatura

```ts theme={null}
import { createHmac, timingSafeEqual } from 'crypto';

const { signature, ...bodyWithoutSignature } = payload;

const expectedSignature = createHmac('sha256', process.env.APP_WEBHOOK_SECRET!)
  .update(JSON.stringify(bodyWithoutSignature))
  .digest('hex');

const sigBuffer = Buffer.from(signature, 'hex');
const expectedBuffer = Buffer.from(expectedSignature, 'hex');

const isValid =
  sigBuffer.length === expectedBuffer.length &&
  timingSafeEqual(sigBuffer, expectedBuffer);
```

<Warning>
  Use sempre `timingSafeEqual` para comparar assinaturas. Comparações diretas com `===` são vulneráveis a timing attacks.
</Warning>

## Respostas

<ResponseField name="received" type="boolean">
  `true` quando o webhook foi processado com sucesso.
</ResponseField>

```json theme={null}
// 200 OK
{ "received": true }

// 400 Bad Request
{ "error": "Payload invalido" }

// 401 Unauthorized
{ "error": "Assinatura ausente" }
{ "error": "Assinatura invalida" }
```

## Testar o Webhook

No painel admin do app-front, acesse a seção de configuração do produto e clique em **"Testar Webhook"**. Isso envia um payload com `is_test: true` para o SSO, sem provisionar acesso real.

<Tip>
  Ao receber `is_test: true`, o SSO deve registrar o evento mas **não** liberar acesso ao usuário. Use isso para validar a configuração do endpoint antes de ir para produção.
</Tip>
