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

# Webhooks

> Configure webhooks para receber notificações do Triglit

O Triglit envia **webhooks** para seus endpoints quando eventos importantes ocorrem durante a execução de workflows, como conclusão, falhas ou pausas. Os webhooks são entregues através do **Svix**, uma plataforma confiável de entrega de webhooks.

## O que são Webhooks?

Webhooks são notificações HTTP enviadas pelo Triglit para seus endpoints quando eventos ocorrem. Isso permite que você reaja a eventos em tempo real sem precisar fazer polling constante.

## Eventos Disponíveis

O Triglit envia os seguintes eventos de execução de workflows:

* **`run.started`**: Execução (run) iniciada
* **`run.completed`**: Execução concluída com sucesso
* **`run.failed`**: Execução falhou
* **`run.cancelled`**: Execução cancelada
* **`run.paused`**: Execução pausada (aguardando retomada)
* **`run.resumed`**: Execução retomada após pausa

## Configuração

### 1. Configurar Webhook no Frontend

A configuração de webhooks é feita através do **painel do Triglit** (frontend), não via API. No painel, você pode:

1. Acessar as configurações de webhooks
2. Adicionar um endpoint para receber notificações
3. Selecionar quais eventos deseja receber
4. Ver o secret para validação

<Note>
  A configuração de webhooks é feita apenas pelo frontend do Triglit. Não há endpoints de API para cadastro de webhooks.
</Note>

### 2. Implementar Endpoint

Como os webhooks são entregues via **Svix**, você precisa validar a assinatura usando o SDK do Svix ou validar manualmente:

<CodeGroup>
  ```javascript JavaScript - Usando SDK do Svix theme={null}
  const svix = require('svix');

  const webhook = new svix.Webhook(process.env.SVIX_SECRET);

  app.post('/webhooks/triglit', express.raw({ type: 'application/json' }), (req, res) => {
    try {
      // Validar assinatura usando Svix
      const payload = webhook.verify(req.body, {
        'svix-id': req.headers['svix-id'],
        'svix-timestamp': req.headers['svix-timestamp'],
        'svix-signature': req.headers['svix-signature']
      });
      
      const event = JSON.parse(payload);
      
      switch (event.type) {
        case 'run.completed':
          handleRunCompleted(event.data);
          break;
        case 'run.failed':
          handleRunFailed(event.data);
          break;
        case 'run.paused':
          handleRunPaused(event.data);
          break;
      }
      
      res.json({ received: true });
    } catch (error) {
      console.error('Webhook validation failed:', error);
      res.status(401).json({ error: 'Invalid signature' });
    }
  });
  ```

  ```python Python - Usando SDK do Svix theme={null}
  from svix import Webhook
  from flask import Flask, request, jsonify
  import os

  app = Flask(__name__)
  webhook = Webhook(os.getenv('SVIX_SECRET'))

  @app.route('/webhooks/triglit', methods=['POST'])
  def handle_webhook():
      try:
          # Validar assinatura usando Svix
          payload = webhook.verify(
              request.data,
              {
                  'svix-id': request.headers.get('svix-id'),
                  'svix-timestamp': request.headers.get('svix-timestamp'),
                  'svix-signature': request.headers.get('svix-signature')
              }
          )
          
          event = json.loads(payload)
          
          if event['type'] == 'run.completed':
              handle_run_completed(event['data'])
          elif event['type'] == 'run.failed':
              handle_run_failed(event['data'])
          
          return jsonify({'received': True})
      except Exception as e:
          return jsonify({'error': 'Invalid signature'}), 401
  ```
</CodeGroup>

## Estrutura do Evento

Os eventos enviados pelo Triglit seguem esta estrutura:

```json theme={null}
{
  "type": "run.completed",
  "data": {
    "runId": "run_456",
    "tenantId": "tenant_abc",
    "subTenantId": "sub_tenant_xyz",
    "workflowVersionId": "version_789",
    "timestamp": "2024-01-15T10:00:00Z",
    "status": "succeeded"
  }
}
```

<Note>
  O campo `subTenantId` é opcional e estará presente apenas quando a execução ocorrer em um ambiente de sub-tenant. Se não houver sub-tenant associado, este campo não será incluído no payload.
</Note>

### Estrutura por Tipo de Evento

**`run.started`**:

```json theme={null}
{
  "type": "run.started",
  "data": {
    "runId": "run_456",
    "tenantId": "tenant_abc",
    "subTenantId": "sub_tenant_xyz",
    "workflowVersionId": "version_789",
    "timestamp": "2024-01-15T10:00:00Z"
  }
}
```

**`run.completed`**:

```json theme={null}
{
  "type": "run.completed",
  "data": {
    "runId": "run_456",
    "tenantId": "tenant_abc",
    "subTenantId": "sub_tenant_xyz",
    "workflowVersionId": "version_789",
    "timestamp": "2024-01-15T10:00:00Z",
    "status": "succeeded"
  }
}
```

**`run.failed`**:

```json theme={null}
{
  "type": "run.failed",
  "data": {
    "runId": "run_456",
    "tenantId": "tenant_abc",
    "subTenantId": "sub_tenant_xyz",
    "workflowVersionId": "version_789",
    "timestamp": "2024-01-15T10:00:00Z",
    "status": "failed",
    "error": "Error message (opcional)"
  }
}
```

**`run.cancelled`**:

```json theme={null}
{
  "type": "run.cancelled",
  "data": {
    "runId": "run_456",
    "tenantId": "tenant_abc",
    "subTenantId": "sub_tenant_xyz",
    "workflowVersionId": "version_789",
    "timestamp": "2024-01-15T10:00:00Z",
    "status": "cancelled"
  }
}
```

**`run.paused`**:

```json theme={null}
{
  "type": "run.paused",
  "data": {
    "runId": "run_456",
    "tenantId": "tenant_abc",
    "subTenantId": "sub_tenant_xyz",
    "workflowVersionId": "version_789",
    "timestamp": "2024-01-15T10:00:00Z",
    "status": "paused",
    "pauseReason": "Waiting for user input",
    "resumeToken": "resume_token_123",
    "timeoutAt": "2024-01-15T10:05:00Z"
  }
}
```

**`run.resumed`**:

```json theme={null}
{
  "type": "run.resumed",
  "data": {
    "runId": "run_456",
    "tenantId": "tenant_abc",
    "subTenantId": "sub_tenant_xyz",
    "workflowVersionId": "version_789",
    "timestamp": "2024-01-15T10:00:00Z",
    "status": "resumed",
    "resumeToken": "resume_token_123"
  }
}
```

## Validação de Assinatura

O Svix envia webhooks com assinaturas nos headers:

* **`svix-id`**: ID único do evento
* **`svix-timestamp`**: Timestamp do evento
* **`svix-signature`**: Assinatura HMAC SHA256

<Warning>
  Sempre valide a assinatura do webhook usando o SDK do Svix ou validação manual. Nunca processe webhooks sem validar a assinatura.
</Warning>

## Exemplo Completo

<CodeGroup>
  ```javascript JavaScript theme={null}
  const express = require('express');
  const { Webhook } = require('svix');

  const app = express();
  const webhook = new Webhook(process.env.SVIX_SECRET);

  app.post('/webhooks/triglit', express.raw({ type: 'application/json' }), async (req, res) => {
    try {
      // Validar assinatura usando Svix
      const payload = webhook.verify(req.body, {
        'svix-id': req.headers['svix-id'],
        'svix-timestamp': req.headers['svix-timestamp'],
        'svix-signature': req.headers['svix-signature']
      });
      
      const event = JSON.parse(payload);
      
      console.log(`Received event: ${event.type}`);
      
      switch (event.type) {
        case 'run.started':
          await handleRunStarted(event.data);
          break;
          
        case 'run.completed':
          await handleRunCompleted(event.data);
          break;
          
        case 'run.failed':
          await handleRunFailed(event.data);
          break;
          
        case 'run.cancelled':
          await handleRunCancelled(event.data);
          break;
          
        case 'run.paused':
          await handleRunPaused(event.data);
          break;
          
        case 'run.resumed':
          await handleRunResumed(event.data);
          break;
      }
      
      res.json({ received: true });
      
    } catch (error) {
      console.error('Webhook error:', error);
      res.status(401).json({ error: 'Invalid signature' });
    }
  });

  async function handleRunStarted(data) {
    console.log(`Run ${data.runId} started`);
    // Lógica para quando uma execução inicia
  }

  async function handleRunCompleted(data) {
    console.log(`Run ${data.runId} completed successfully`);
    // Atualizar status no seu sistema
    // await updateOrderStatus(data.runId, 'processed');
  }

  async function handleRunFailed(data) {
    console.error(`Run ${data.runId} failed:`, data.error);
    // Atualizar status e notificar equipe
    // await updateOrderStatus(data.runId, 'failed');
    // await notifyTeam(data.runId, data.error);
  }

  async function handleRunCancelled(data) {
    console.log(`Run ${data.runId} was cancelled`);
    // Lógica para execução cancelada
  }

  async function handleRunPaused(data) {
    console.log(`Run ${data.runId} paused: ${data.pauseReason}`);
    // Salvar resumeToken para retomar depois
    // await saveResumeToken(data.runId, data.resumeToken);
  }

  async function handleRunResumed(data) {
    console.log(`Run ${data.runId} resumed`);
    // Lógica para quando execução é retomada
  }

  app.listen(3000);
  ```

  ```python Python theme={null}
  from flask import Flask, request, jsonify
  from svix import Webhook
  import os
  import json

  app = Flask(__name__)
  webhook = Webhook(os.getenv('SVIX_SECRET'))

  @app.route('/webhooks/triglit', methods=['POST'])
  def handle_webhook():
      try:
          # Validar assinatura usando Svix
          payload = webhook.verify(
              request.data,
              {
                  'svix-id': request.headers.get('svix-id'),
                  'svix-timestamp': request.headers.get('svix-timestamp'),
                  'svix-signature': request.headers.get('svix-signature')
              }
          )
          
          event = json.loads(payload)
          event_type = event.get('type')
          event_data = event.get('data', {})
          
          if event_type == 'run.completed':
              handle_run_completed(event_data)
          elif event_type == 'run.failed':
              handle_run_failed(event_data)
          elif event_type == 'run.paused':
              handle_run_paused(event_data)
          
          return jsonify({'received': True})
      except Exception as e:
          print(f'Webhook error: {e}')
          return jsonify({'error': 'Invalid signature'}), 401

  def handle_run_completed(data):
      print(f"Run {data['runId']} completed successfully")

  def handle_run_failed(data):
      print(f"Run {data['runId']} failed: {data.get('error')}")

  def handle_run_paused(data):
      print(f"Run {data['runId']} paused: {data.get('pauseReason')}")

  if __name__ == '__main__':
      app.run(port=3000)
  ```
</CodeGroup>

## Retries e Confiabilidade

O **Svix** gerencia automaticamente retries e entrega confiável de webhooks:

* **Retries automáticos**: O Svix retenta automaticamente em caso de falha
* **Entrega garantida**: Webhooks são entregues de forma confiável
* **Timeout**: Configurável no Svix
* **Dead Letter Queue**: Webhooks que falham persistentemente são armazenados

Sempre retorne `200 OK` rapidamente após validar a assinatura:

```javascript theme={null}
app.post('/webhooks/triglit', async (req, res) => {
  try {
    // Validar assinatura
    const payload = webhook.verify(req.body, {
      'svix-id': req.headers['svix-id'],
      'svix-timestamp': req.headers['svix-timestamp'],
      'svix-signature': req.headers['svix-signature']
    });
    
    // Retornar imediatamente
    res.json({ received: true });
    
    // Processar de forma assíncrona
    processEventAsync(JSON.parse(payload));
  } catch (error) {
    res.status(401).json({ error: 'Invalid signature' });
  }
});
```

## Boas Práticas

<AccordionGroup>
  <Accordion title="Validação de Assinatura">
    Sempre valide assinaturas usando o SDK do Svix. Nunca processe webhooks sem validação.
  </Accordion>

  <Accordion title="Idempotência">
    Torne handlers idempotentes para lidar com retries do Svix. Use o `svix-id` para detectar duplicatas.
  </Accordion>

  <Accordion title="Resposta Rápida">
    Retorne 200 OK rapidamente após validar a assinatura e processe eventos de forma assíncrona.
  </Accordion>

  <Accordion title="Logging">
    Logue todos os eventos recebidos usando o `svix-id` para rastreabilidade.
  </Accordion>

  <Accordion title="HTTPS">
    Use sempre HTTPS para webhooks em produção. O Svix requer HTTPS para endpoints de produção.
  </Accordion>

  <Accordion title="Configuração no Frontend">
    Configure webhooks através do painel do Triglit. Não é necessário usar a API para isso.
  </Accordion>
</AccordionGroup>

## SDKs do Svix

O Svix oferece SDKs oficiais para várias linguagens:

* **JavaScript/TypeScript**: `npm install svix`
* **Python**: `pip install svix`
* **Go**: `go get github.com/svix/svix-libs/go`
* **Ruby**: `gem install svix`
* **Java**: `mvn install svix`
* **C#**: `dotnet add package Svix`

<Card title="Documentação do Svix" icon="external-link" href="https://docs.svix.com/app-portal">
  Veja a documentação completa do Svix para mais detalhes sobre validação e configuração
</Card>

<Tip>
  Use o Svix CLI ou dashboard para testar webhooks localmente durante desenvolvimento. O Svix também oferece ferramentas para debugging.
</Tip>
