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

# Webhooks

> Receba um retorno de chamada HTTP no momento em que um projeto atinge um marco, em vez de sondar

# Webhooks

Webhooks permitem que VoiceCheap notifique seu servidor assim que algo acontece, para que você possa parar de sondar
`GET /v1/translate/{projectId}/status`.

<Note>
  As entregas são tentadas **uma vez**. Ainda não há novas tentativas, portanto, continue sondando como uma rede de segurança para
  qualquer coisa que você não possa perder. Novas tentativas com espera exponencial estão planejadas.
</Note>

## Configuração

1. Abra a [página da API](https://voicecheap.ai/page-api) em sua conta VoiceCheap.
2. Em **Webhooks**, selecione **Gerar segredo de assinatura**. O segredo começa com `whsec_` e é mostrado
   **uma vez** — copie-o e armazene-o em seu servidor.
3. Insira sua **URL de endpoint** e salve-a. Ela deve usar `https`.

Isso é tudo. Cada projeto que você iniciar a partir de então entregará eventos para esse endpoint.

### Substituindo o endpoint por solicitação

`POST /v1/translate` e `POST /v1/projects` aceitam um campo opcional `webhookUrl` que substitui o
endpoint da conta apenas para aquele projeto. É útil para enviar tráfego de teste para outro lugar:

```bash theme={null}
curl -X POST "https://api.voicecheap.ai/v1/projects" \
  -H "x-api-key: vc_your-key" \
  -F "file=@interview.mp4" \
  -F "targetLanguage=german" \
  -F "webhookUrl=https://staging.your-server.com/voicecheap/webhooks"
```

O segredo de assinatura é sempre o segredo da conta; apenas o destino muda.

## Eventos

| Evento                          | Dispara quando                                                                            |
| ------------------------------- | ----------------------------------------------------------------------------------------- |
| `project.created`               | O projeto existe e sua transcrição está armazenada. Este é o seu sinal para baixar o SRT. |
| `project.creation.failed`       | O projeto não pôde ser criado.                                                            |
| `project.translation.completed` | A tradução terminou e as saídas estão prontas.                                            |
| `project.translation.failed`    | A tradução falhou.                                                                        |
| `project.lipsync.completed`     | Sincronização labial concluída.                                                           |
| `project.lipsync.failed`        | A sincronização labial falhou.                                                            |

<Note>
  Não existe um evento de transcrição separado. A transcrição é executada durante a criação do projeto, e um projeto
  só existe quando sua transcrição é armazenada — portanto, `project.created` já significa que a transcrição está
  pronta para ser buscada com [`POST /v1/projects/{projectId}/transcript`](/docs/pt/api-reference/project-transcript).
</Note>

## Payload

Cada entrega é um `POST` com um corpo JSON:

```json theme={null}
{
  "type": "project.created",
  "eventId": "evt_9f2c4b1d8e7a4c3f9b2d1e0a5c6b7d8e",
  "projectId": "7fa7d3a3-4f2b-4c1e-9a6d-2b3c4d5e6f70",
  "workflow": "transcription",
  "status": "success",
  "targetLanguage": "german",
  "error": null,
  "occurredAt": "2026-08-19T09:12:00.000Z"
}
```

| Campo            | Tipo           | Descrição                                                                                                                 |
| ---------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `type`           | string         | Um dos eventos acima.                                                                                                     |
| `eventId`        | string         | Único por evento. Use-o para desduplicar.                                                                                 |
| `projectId`      | string         | O projeto ao qual este evento pertence.                                                                                   |
| `workflow`       | string         | `transcription` ou `translation`.                                                                                         |
| `status`         | string         | `success` ou `failed`.                                                                                                    |
| `targetLanguage` | string         | Presente apenas em eventos específicos de idioma.                                                                         |
| `error`          | object \| null | Em eventos `*.failed`, o mesmo formato `{ code, message }` da API REST. Veja [Códigos de Erro](/docs/pt/api-reference/errors). |
| `occurredAt`     | string         | Timestamp ISO 8601.                                                                                                       |

O payload é intencionalmente leve. Chame a API REST para obter o conteúdo em si.

## Verificando a assinatura

Cada entrega carrega estes cabeçalhos:

| Cabeçalho                | Descrição                                                                           |
| ------------------------ | ----------------------------------------------------------------------------------- |
| `X-VoiceCheap-Signature` | `sha256=<hex>` — HMAC-SHA256 do payload, codificado pelo seu segredo de assinatura. |
| `X-VoiceCheap-Timestamp` | Segundos Unix, incluídos na string assinada para que você possa rejeitar replays.   |
| `X-VoiceCheap-Event-Id`  | Mesmo valor que `eventId` no corpo, útil em solicitações de suporte.                |

A assinatura cobre `<timestamp>.<raw body>`, portanto, é diferente em cada entrega e prova tanto
que a solicitação veio do VoiceCheap quanto que o corpo não foi modificado em trânsito.

```js theme={null}
import crypto from 'crypto';
import express from 'express';

const app = express();

// The raw body is required: parsing and re-serializing changes the bytes and the signature will
// never match. This is the single most common cause of failed verification.
app.post('/voicecheap/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body.toString('utf8');
  const timestamp = req.header('X-VoiceCheap-Timestamp');
  const signature = req.header('X-VoiceCheap-Signature')?.replace('sha256=', '') ?? '';

  // Reject anything older than five minutes.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return res.status(400).send('Stale timestamp');
  }

  const expected = crypto
    .createHmac('sha256', process.env.VOICECHEAP_WEBHOOK_SECRET)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const isValid =
    expected.length === signature.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));

  if (!isValid) return res.status(401).send('Invalid signature');

  const event = JSON.parse(rawBody);
  // Acknowledge quickly, then process asynchronously.
  res.status(200).send('ok');
  handleEvent(event);
});
```

<Warning>
  Verifique a assinatura antes de confiar em uma entrega. Seu endpoint é uma URL pública, e a assinatura é
  o que distingue um evento real VoiceCheap de qualquer outra coisa que chegue até ele.
</Warning>

## Respondendo

Responda com qualquer status `2xx` para confirmar o recebimento. Responda dentro de **10 segundos** — confirme primeiro e faça
o trabalho depois, em vez de processar antes de responder.

Uma resposta que não seja 2xx ou um tempo limite é registrada como uma entrega falha e, por enquanto, não é tentada novamente.

## Rotacionando o segredo

Selecione **Rotacionar** na página da API para gerar um novo segredo. O anterior para de funcionar
imediatamente, portanto, implemente o novo segredo em seu servidor assim que rotacionar.

Excluir o webhook remove tanto o endpoint quanto o segredo, e as entregas param.
