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

# Алгоритм генерації підпису

Підпис охоплює повний payload вебхука: `id, delivered_at, event`. Підтримуються два алгоритми підпису залежно від keyType:

## ED25519 (Рекомендований)

1. Створити об’єкт payload: `id, delivered_at, event`
2. JSON.stringify(payload)
3. Закодувати JSON-рядок у Base64
4. Підписати за допомогою ED25519 (ed25519.signAsync)
5. Закодувати підпис у Base64

## Legacy (Застарілий)

1. Створити об’єкт payload: `id, delivered_at, event`
2. JSON.stringify(payload)
3. Закодувати JSON-рядок у Base64
4. SHA256 хеш (privateKey + data)
5. Закодувати хеш у Base64

## Особливості безпеки:

* Підпис охоплює всі поля, включно з id і delivered\_at
* Унікальний id запобігає атакам повторного відтворення (replay attacks)
* Валідація часової мітки запобігає прийняттю застарілих вебхуків (вікно 16 хвилин)
* 16-хвилинне вікно враховує повторні спроби (негайно + 5 хв + 10 хв) + мережевий буфер
* ED25519 забезпечує криптографічну автентичність

## Логіка повторної спроби

1. Перша спроба: Негайно
2. Друга спроба: затримка 300 секунд (5 хвилин)
3. Третя спроба: затримка 600 секунд (10 хвилин)
4. Помилка: задача видаляється з черги та логуються

***

<Warning>
  **ВАЖЛИВО:** Завжди перевіряйте webhooks, щоб запобігти підробці та повторним атакам.
</Warning>

## Повний алгоритм перевірки

```typescript theme={null}
import * as ed25519 from '@noble/ed25519';

interface WebhookPayload {
  id: string;
  delivered_at: string;
  event: {
    event_type: string;
    timestamp: string;
    data: any;
  };
  signature: string;
}

async function verifyWebhook(
  webhook: WebhookPayload,
  publicKey: string,
  seenWebhookIds: Set<string>
): Promise<boolean> {
  // 1. Replay Protection: Check if webhook ID already processed
  if (seenWebhookIds.has(webhook.id)) {
    throw new Error('Replay attack detected: webhook ID already processed');
  }

  // 2. Timestamp Validation: Reject webhooks older than 16 minutes or from future
  // Max delivery time: 15 min (immediate + 5min retry + 10min retry) + 1min network buffer
  const deliveredAt = new Date(webhook.delivered_at);
  const now = Date.now();
  const timeDiff = Math.abs(now - deliveredAt.getTime());

  if (timeDiff > 960000) { // 16 minutes
    throw new Error('Webhook timestamp outside acceptable window');
  }

  // 3. Signature Verification: Verify signature covers entire payload
  const payload = {
    id: webhook.id,
    delivered_at: webhook.delivered_at,
    event: webhook.event
  };

  // Reconstruct signed data (same as server)
  const json = JSON.stringify(payload);
  const data = Buffer.from(json, 'utf8').toString('base64');

  // Decode signature and public key
  const signatureBytes = Buffer.from(webhook.signature, 'base64');
  const publicKeyBytes = Buffer.from(publicKey, 'hex');
  const dataBytes = Buffer.from(data, 'utf8');

  // Verify with ED25519
  const isValid = await ed25519.verifyAsync(signatureBytes, dataBytes, publicKeyBytes);

  if (!isValid) {
    throw new Error('Invalid signature: webhook authentication failed');
  }

  // 4. Store webhook ID to prevent future replay
  seenWebhookIds.add(webhook.id);

  return true;
}
```

***

## Етапи перевірки ED25519

1. Захист від повторного відтворення - перевірте **`webhook.id`**, який не був помічений раніше
2. Перевірка часової мітки - перевірка delivery\_at протягом 16 хвилин (максимальний час доставки + буфер)
3. Відновлення корисної нагрузки - `id, delivery_at, event`
4. Перевірка підпису - ed25519.verifyAsync (signature, payload, publicKey)
5. Зберегти ідентифікатор веб-гука - зберегти для запобігання повторному відтворенню
