# Overenie a testovanie (/sk/webhooks/receiving)



Pred použitím payloadu overte každý webhook a potom odošlite test, ktorým potvrdíte, že váš prijímač funguje od začiatku do konca.

## Skôr ako začnete [#before-you-start]

* [Uložená cieľová adresa](/webhooks/configuration#save-a-destination) a jej signing secret (podpisový kľúč) `twhsec_...`. Na overovanie webhookov nepoužívajte svoj Server Secret Token.
* Cesta prijímača, ktorá dokáže načítať pôvodné telo požiadavky skôr, než ho zmení akýkoľvek parser JSON.
* Úložisko s obmedzením jedinečnosti na `id` udalosti, aby boli duplicity bezpečné.

## Prijmite webhook [#receive-a-webhook]

<div className="fd-steps [&_h3]:fd-step">
  ### Načítajte pôvodné telo [#read-the-raw-body]

  Tapaya odosiela JSON `POST` s týmito hlavičkami. Telo ponechajte ako pôvodné bajty; rozparsované a znova serializované telo už nezodpovedá podpisu.

  ```http
  Content-Type: application/json
  X-Tapaya-Event-Id: 019a0000-0000-7000-8000-000000000001
  X-Tapaya-Signature: t=1793361600,v1=<lowercase hexadecimal HMAC-SHA256>
  ```

  * V Express použite pre túto cestu `express.raw({ type: "application/json" })` pred globálnym parserom JSON.

  ### Skontrolujte podpis [#check-the-signature]

  Vypočítajte HMAC-SHA256 z časovej pečiatky, bodky a pôvodného tela a potom ho porovnajte s `v1` v konštantnom čase.

  ```js title="verify-tapaya-webhook.mjs"
  import { createHmac, timingSafeEqual } from "node:crypto";

  export function verifyTapayaWebhook(rawBody, signatureHeader, secret, now = Date.now()) {
    if (!Buffer.isBuffer(rawBody) || typeof signatureHeader !== "string") {
      throw new Error("Missing raw body or signature");
    }
    if (typeof secret !== "string" || !secret.startsWith("twhsec_")) {
      throw new Error("Missing signing secret");
    }

    const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(signatureHeader);
    if (!match) throw new Error("Malformed signature");

    const timestamp = Number(match[1]);
    if (!Number.isSafeInteger(timestamp) || Math.abs(now / 1000 - timestamp) > 300) {
      throw new Error("Expired signature");
    }

    const expected = createHmac("sha256", Buffer.from(secret, "utf8"))
      .update(`${match[1]}.`, "utf8")
      .update(rawBody)
      .digest();
    const received = Buffer.from(match[2], "hex");
    if (!timingSafeEqual(expected, received)) throw new Error("Invalid signature");

    return JSON.parse(rawBody.toString("utf8"));
  }
  ```

  * Celý kľúč `twhsec_...` použite ako kľúč v UTF-8. Nedekódujte ho a neodstraňujte z neho prefix.
  * Odmietnite časové pečiatky, ktoré sa od vašich hodín líšia o viac než päť minút, a udržiavajte hodiny synchronizované.
  * Počas [rotácie signing secret](/webhooks/configuration#configuration-changes) najskôr skúste nový kľúč, potom starý, kým sa front nevyprázdni.

  ### Uložte a potvrďte [#store-and-acknowledge]

  Udalosť prijmite až po jej bezpečnom uložení, aby chyba na vašej strane vyvolala opakovaný pokus.

  ```js title="server.mjs"
  import express from "express";
  import { verifyTapayaWebhook } from "./verify-tapaya-webhook.mjs";
  import { saveEventOnce } from "./events.mjs"; // your store, unique on event id

  const app = express();

  app.post(
    "/webhooks/tapaya",
    express.raw({ type: "application/json" }),
    async (req, res) => {
      let event;
      try {
        event = verifyTapayaWebhook(
          req.body,
          req.get("X-Tapaya-Signature"),
          process.env.TAPAYA_WEBHOOK_SECRET,
        );
      } catch {
        return res.sendStatus(400);
      }
      if (event.organizationId !== process.env.TAPAYA_ORGANIZATION_ID) {
        return res.sendStatus(400);
      }

      await saveEventOnce(event); // skips an id it already stored
      res.sendStatus(204);
    },
  );
  ```

  * Pre udalosť, ktorú ste už uložili, vráťte `2xx`. Opakované pokusy zachovávajú rovnaké `id` a telo, ale majú novú časovú pečiatku a podpis.
  * Ak novú udalosť nemôžete uložiť, vráťte odpoveď mimo `2xx`, aby doručovanie pokus zopakovalo.
  * Uložené udalosti spracúvajte z vlastného frontu. Poradie doručenia nie je zaručené, preto staršie snímky odsúhlaste s aktuálnym stavom.
</div>

## Odošlite testovací webhook [#send-a-test-webhook]

Testovacia akcia v Platform odošle jednu podpísanú požiadavku `webhook.test` na uloženú URL. Funguje aj vtedy, keď sú webhooky vypnuté alebo typ nie je vo vašom odbere, a nevytvára žiadnu platbu, refundáciu ani obchodníka.

1. Otvorte stránku **API Keys** svojej organizácie a uložte cieľovú adresu.
2. Použite testovaciu akciu webhooku.
3. Skontrolujte výsledok, trvanie a odoslaný JSON zobrazené v Platform.

```json
{
  "id": "019a0000-0000-7000-8000-000000000010",
  "type": "webhook.test",
  "apiVersion": "1",
  "organizationId": "019a0000-0000-7000-8000-000000000002",
  "merchantId": null,
  "createdAt": "2026-10-30T12:00:00.000000Z",
  "test": true,
  "data": {
    "message": "This is a test webhook from Tapaya. No payment was created."
  }
}
```

<Accordions type="multiple">
  <Accordion title="Čo test pokrýva" id="what-a-test-covers">
    * Jeden pokus s časovým limitom 10 sekúnd a bez automatického opakovania.
    * Kontroluje doručenie a spracovanie podpisu, nie payloady platieb ani onboardingu. Tie otestujte aktivitou v sandboxe.
    * Neúspešné doručenie sa zobrazí ako výsledok testu, nie ako chyba v Platform.
  </Accordion>

  <Accordion title="Limity testov" id="test-limits">
    * Doručenia z frontu a ručné testy zdieľajú limit 2 súčasných požiadaviek na organizáciu.
    * Ručné testy navyše umožňujú 10 spustení za pohyblivých 60 sekúnd na organizáciu, spoločne pre všetkých používateľov.
    * Po prekročení ktoréhokoľvek limitu test vráti `429` s `Retry-After` v sekundách a nič neodošle.
    * Prijaté testy sa započítavajú aj pri chybe alebo zrušení. Opätovné vytvorenie cieľovej adresy počet neobnoví.
  </Accordion>
</Accordions>

## Lokálne a automatizačné prijímače [#local-and-automation-receivers]

Ak váš produkčný server ešte nie je pripravený, nasmerujte webhook na vývojársky počítač alebo na no-code nástroj.

<Accordions type="multiple">
  <Accordion title="Otestujte lokálny prijímač" id="test-a-local-receiver">
    * Ako cieľovú adresu uložte URL verejného HTTPS tunela; URL localhostu a súkromné IP adresy sú odmietnuté.
    * Ponechajte zapnuté overovanie podpisu a uistite sa, že tunel bez úprav preposiela pôvodné telo a hlavičky Tapaya.
    * Keď sa URL tunela zmení, cieľovú adresu znova uložte. Pôvodný tunel ponechajte spustený, kým sa udalosti, ktoré naň čakajú vo fronte, nevyprázdnia.
  </Accordion>

  <Accordion title="Pripojte automatizačný nástroj" id="connect-an-automation-tool">
    * Vstupný bod nesmie vyžadovať vlastné autentifikačné hlavičky, musí zachovať pôvodné telo a odpovedať do 10 sekúnd.
    * Musí sám overiť HMAC pôvodného tela, alebo byť umiestnený za overovacím adaptérom, ktorý to robí.
    * Keď nástroj odpovie `2xx`, doručenie sa považuje za úspešné. Neskoršie zlyhania pracovného postupu a duplicity rieši nástroj; nevyvolajú ďalšie doručenie.
  </Accordion>
</Accordions>

## Riešenie chýb [#handle-errors]

<Accordions type="multiple">
  <Accordion title="Výsledky testov" id="test-results">
    | Výsledok          | Význam                                                                              |
    | ----------------- | ----------------------------------------------------------------------------------- |
    | `delivered: true` | Prijímač vrátil HTTP `2xx`.                                                         |
    | `http_error`      | Prijímač vrátil stav mimo `2xx`.                                                    |
    | `transport_error` | Cieľová adresa nebola dostupná vrátane chýb TLS, DNS alebo overenia verejnej siete. |
    | `timeout`         | Prijímač nepotvrdil prijatie do 10 sekúnd.                                          |
  </Accordion>

  <Accordion title="Kontroly podpisu" id="signature-checks">
    | Príznak           | Čo skontrolovať                                                                                                    |
    | ----------------- | ------------------------------------------------------------------------------------------------------------------ |
    | Nezhodný podpis   | Použite celý kľúč a pôvodné bajty. Preformátovaný JSON alebo API token použitý ako kľúč spôsobí zlyhanie overenia. |
    | Expirovaný podpis | Synchronizujte hodiny prijímača a overujte podľa hlavičky požiadavky, nie podľa `createdAt`.                       |
  </Accordion>

  <Accordion title="Doručovanie" id="delivery">
    | Príznak                                       | Čo skontrolovať                                                                                                                                                   |
    | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | Odmietnutá URL alebo zablokované pripojenie   | Použite verejné HTTPS na porte 443 bez presmerovaní, prihlasovacích údajov alebo fragmentu.                                                                       |
    | Chýbajúca udalosť webhooku                    | Skontrolujte organizáciu, stav zapnutia a vybrané typy udalostí. Minulé zmeny sa spätne nedopĺňajú.                                                               |
    | Duplicitná alebo staršia udalosť              | Deduplikujte podľa `id` a namiesto predpokladania poradia vykonajte odsúhlasenie s aktuálnym stavom. Pozrite si [pravidlá doručovania](/webhooks#delivery-rules). |
    | Test funguje, ale spracovanie platieb zlyháva | `webhook.test` má inú štruktúru `data`. Smerujte podľa `type` a testujte so skutočnými udalosťami zo sandboxu.                                                    |
  </Accordion>
</Accordions>
