> ## 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.

# Webhook

> Ricevi un callback HTTP nel momento in cui un progetto raggiunge una pietra miliare, invece di eseguire il polling

# Webhook

I webhook consentono a VoiceCheap di notificare il tuo server non appena accade qualcosa, così puoi interrompere il polling
`GET /v1/translate/{projectId}/status`.

<Note>
  I tentativi di consegna vengono effettuati **una volta**. Non ci sono ancora tentativi di riprova, quindi mantieni il polling come rete di sicurezza per
  tutto ciò che non puoi permetterti di perdere. Sono previsti tentativi di riprova con backoff esponenziale.
</Note>

## Configurazione

1. Apri la [pagina API](https://voicecheap.ai/page-api) nel tuo account VoiceCheap.
2. In **Webhook**, seleziona **Genera segreto di firma**. Il segreto inizia con `whsec_` e viene mostrato
   **una volta** — copialo e salvalo sul tuo server.
3. Inserisci il tuo **URL dell'endpoint** e salvalo. Deve utilizzare `https`.

Tutto qui. Ogni progetto che avvii da quel momento in poi invierà eventi a quell'endpoint.

### Sovrascrittura dell'endpoint per richiesta

`POST /v1/translate` e `POST /v1/projects` accettano un campo opzionale `webhookUrl` che sovrascrive
l'endpoint dell'account solo per quel progetto. È utile per inviare il traffico di staging altrove:

```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"
```

Il segreto di firma è sempre il segreto dell'account; cambia solo la destinazione.

## Eventi

| Evento                          | Si attiva quando                                                                                    |
| ------------------------------- | --------------------------------------------------------------------------------------------------- |
| `project.created`               | Il progetto esiste e la sua trascrizione è archiviata. Questo è il tuo segnale per scaricare l'SRT. |
| `project.creation.failed`       | Il progetto non ha potuto essere creato.                                                            |
| `project.translation.completed` | La traduzione è terminata e gli output sono pronti.                                                 |
| `project.translation.failed`    | La traduzione non è riuscita.                                                                       |
| `project.lipsync.completed`     | Sincronizzazione labiale completata.                                                                |
| `project.lipsync.failed`        | Sincronizzazione labiale non riuscita.                                                              |

<Note>
  Non esiste un evento di trascrizione separato. La trascrizione viene eseguita durante la creazione del progetto e un progetto
  esiste solo una volta che la sua trascrizione è archiviata — quindi `project.created` significa già che la trascrizione è
  pronta per essere recuperata con [`POST /v1/projects/{projectId}/transcript`](/docs/it/api-reference/project-transcript).
</Note>

## Payload

Ogni consegna è un `POST` con un 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           | Descrizione                                                                                                                    |
| ---------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `type`           | string         | Uno degli eventi sopra indicati.                                                                                               |
| `eventId`        | string         | Univoco per evento. Usalo per la deduplicazione.                                                                               |
| `projectId`      | string         | Il progetto a cui appartiene questo evento.                                                                                    |
| `workflow`       | string         | `transcription` o `translation`.                                                                                               |
| `status`         | string         | `success` o `failed`.                                                                                                          |
| `targetLanguage` | string         | Presente solo negli eventi specifici della lingua.                                                                             |
| `error`          | object \| null | Negli eventi `*.failed`, la stessa forma `{ code, message }` dell'API REST. Vedi [Codici di errore](/docs/it/api-reference/errors). |
| `occurredAt`     | string         | Timestamp ISO 8601.                                                                                                            |

Il payload è intenzionalmente leggero. Chiama l'API REST per il contenuto stesso.

## Verifica della firma

Ogni consegna trasporta queste intestazioni:

| Intestazione             | Descrizione                                                                         |
| ------------------------ | ----------------------------------------------------------------------------------- |
| `X-VoiceCheap-Signature` | `sha256=<hex>` — HMAC-SHA256 del payload, codificato dal tuo segreto di firma.      |
| `X-VoiceCheap-Timestamp` | Secondi Unix, inclusi nella stringa firmata in modo da poter rifiutare le repliche. |
| `X-VoiceCheap-Event-Id`  | Stesso valore di `eventId` nel corpo, utile nelle richieste di supporto.            |

La firma copre `<timestamp>.<raw body>`, quindi è diversa a ogni consegna e dimostra sia
che la richiesta proviene da VoiceCheap sia che il corpo non è stato modificato durante il transito.

```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>
  Verifica la firma prima di considerare attendibile una consegna. Il tuo endpoint è un URL pubblico e la firma è
  ciò che distingue un vero evento VoiceCheap da qualsiasi altra cosa che lo raggiunga.
</Warning>

## Risposta

Rispondi con qualsiasi stato `2xx` per confermare la ricezione. Rispondi entro **10 secondi** — conferma prima e svolgi
il lavoro in seguito, invece di elaborare prima di rispondere.

Una risposta non 2xx o un timeout viene registrato come consegna non riuscita e, per ora, non viene riprovato.

## Rotazione del segreto

Seleziona **Ruota** nella pagina API per generare un nuovo segreto. Il precedente smette di funzionare
immediatamente, quindi distribuisci il nuovo segreto sul tuo server non appena lo ruoti.

L'eliminazione del webhook rimuove sia l'endpoint che il segreto e le consegne si interrompono.
