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

# Webhooki

> Otrzymuj wywołanie zwrotne HTTP w momencie, gdy projekt osiągnie kamień milowy, zamiast odpytywać

# Webhooki

Webhooki pozwalają VoiceCheap powiadomić Twój serwer, gdy tylko coś się wydarzy, dzięki czemu możesz przestać odpytywać
`GET /v1/translate/{projectId}/status`.

<Note>
  Dostarczenia są podejmowane **raz**. Nie ma jeszcze ponownych prób, więc zachowaj odpytywanie jako zabezpieczenie dla
  wszystkiego, czego nie możesz przegapić. Planowane są ponowne próby z wykładniczym wycofaniem.
</Note>

## Konfiguracja

1. Otwórz [stronie API](https://voicecheap.ai/page-api) na swoim koncie VoiceCheap.
2. W sekcji **Webhooki** wybierz **Generuj klucz podpisu**. Klucz zaczyna się od `whsec_` i jest wyświetlany
   **raz** — skopiuj go i zapisz na swoim serwerze.
3. Wprowadź swój **adres URL punktu końcowego** i zapisz go. Musi on używać `https`.

To wszystko. Każdy projekt, który od tego momentu uruchomisz, będzie wysyłał zdarzenia do tego punktu końcowego.

### Zastępowanie punktu końcowego dla każdego żądania

`POST /v1/translate` i `POST /v1/projects` akceptują opcjonalne pole `webhookUrl`, które zastępuje
punkt końcowy konta tylko dla tego projektu. Jest to przydatne do wysyłania ruchu testowego gdzie indziej:

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

Klucz podpisu jest zawsze kluczem konta; zmienia się tylko miejsce docelowe.

## Zdarzenia

| Zdarzenie                       | Wyzwalane, gdy                                                                        |
| ------------------------------- | ------------------------------------------------------------------------------------- |
| `project.created`               | Projekt istnieje, a jego transkrypcja jest zapisana. To sygnał do pobrania pliku SRT. |
| `project.creation.failed`       | Nie udało się utworzyć projektu.                                                      |
| `project.translation.completed` | Tłumaczenie zostało zakończone, a dane wyjściowe są gotowe.                           |
| `project.translation.failed`    | Tłumaczenie nie powiodło się.                                                         |
| `project.lipsync.completed`     | Synchronizacja ruchu ust zakończona.                                                  |
| `project.lipsync.failed`        | Synchronizacja ruchu ust nie powiodła się.                                            |

<Note>
  Nie ma oddzielnego zdarzenia transkrypcji. Transkrypcja uruchamia się podczas tworzenia projektu, a projekt
  istnieje dopiero wtedy, gdy jego transkrypcja jest zapisana — więc `project.created` oznacza już, że transkrypcja jest
  gotowa do pobrania za pomocą [`POST /v1/projects/{projectId}/transcript`](/docs/pl/api-reference/project-transcript).
</Note>

## Ładunek (Payload)

Każde dostarczenie to `POST` z treścią 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"
}
```

| Pole             | Typ            | Opis                                                                                                                         |
| ---------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `type`           | string         | Jedno z powyższych zdarzeń.                                                                                                  |
| `eventId`        | string         | Unikalne dla każdego zdarzenia. Użyj go do usunięcia duplikatów.                                                             |
| `projectId`      | string         | Projekt, do którego należy to zdarzenie.                                                                                     |
| `workflow`       | string         | `transcription` lub `translation`.                                                                                           |
| `status`         | string         | `success` lub `failed`.                                                                                                      |
| `targetLanguage` | string         | Obecne tylko w zdarzeniach specyficznych dla języka.                                                                         |
| `error`          | object \| null | W zdarzeniach `*.failed`, ten sam kształt `{ code, message }` co w REST API. Zobacz [Kody błędów](/docs/pl/api-reference/errors). |
| `occurredAt`     | string         | Znacznik czasu ISO 8601.                                                                                                     |

Ładunek jest celowo ograniczony. Wywołaj REST API, aby uzyskać samą treść.

## Weryfikacja podpisu

Każde dostarczenie zawiera te nagłówki:

| Nagłówek                 | Opis                                                                           |
| ------------------------ | ------------------------------------------------------------------------------ |
| `X-VoiceCheap-Signature` | `sha256=<hex>` — HMAC-SHA256 ładunku, kluczowany Twoim tajnym kluczem podpisu. |
| `X-VoiceCheap-Timestamp` | Sekundy Unix, zawarte w podpisanym ciągu, aby można było odrzucić powtórzenia. |
| `X-VoiceCheap-Event-Id`  | Ta sama wartość co `eventId` w treści, przydatna w zgłoszeniach do wsparcia.   |

Podpis obejmuje `<timestamp>.<raw body>`, więc jest inny przy każdym dostarczeniu i dowodzi zarówno
tego, że żądanie pochodzi od VoiceCheap, jak i tego, że treść nie została zmodyfikowana w trakcie przesyłania.

```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>
  Zweryfikuj podpis przed zaufaniem dostarczeniu. Twój punkt końcowy jest publicznym adresem URL, a podpis jest
  tym, co odróżnia prawdziwe zdarzenie VoiceCheap od wszystkiego innego, co do niego dociera.
</Warning>

## Odpowiadanie

Odpowiedz dowolnym statusem `2xx`, aby potwierdzić odbiór. Odpowiedz w ciągu **10 sekund** — najpierw potwierdź, a potem
wykonaj pracę, zamiast przetwarzać przed wysłaniem odpowiedzi.

Odpowiedź inna niż 2xx lub przekroczenie czasu jest rejestrowane jako nieudane dostarczenie i na razie nie jest ponawiane.

## Rotacja klucza

Wybierz **Rotuj** na stronie API, aby wygenerować nowy klucz. Poprzedni przestaje działać
natychmiast, więc wdróż nowy klucz na swój serwer zaraz po rotacji.

Usunięcie webhooka usuwa zarówno punkt końcowy, jak i klucz, a dostarczanie zostaje zatrzymane.
