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

# Webhook-uri

> Înregistrează un endpoint, verifică semnătura Standard Webhooks a unei notificări, elimină duplicatele și aplic-o, și ce se întâmplă când endpoint-ul tău nu răspunde.

<Warning>
  **Preview: nimic nu este trimis încă.** Pagina descrie contractul API-ului de distribuție (versiunea `1.0.0`). Nicio notificare nu este livrată până nu se deschide sandbox-ul.
</Warning>

Un webhook este o notificare subțire: spune ce s-a schimbat și numește resursa, iar tu citești resursa numită. Notificările urmează [Standard Webhooks](https://www.standardwebhooks.com/), așa că o bibliotecă pentru acest standard în limbajul tău le poate verifica.

## Înregistrează un endpoint

`POST /webhooks` (permisiunea `manage:webhooks`) înregistrează un endpoint, iar canalul tău poate avea cel mult cinci:

```json theme={null}
{
  "url": "https://listings.example.com/hooks/distribution",
  "events": ["property.*", "availability.updated"],
  "description": "Production listener"
}
```

* `url` este o adresă HTTPS publică, pe portul 443. O țintă privată, rezervată, loopback sau link-local este refuzată (`url_not_allowed`), iar redirecționările nu sunt urmate niciodată.
* `events` listează cel mult 20 de tipuri de evenimente sau tipare (`*`, `property.*`, `availability.updated`), fiecare acoperit de permisiunile cheii tale.
* Răspunsul conține `secret`-ul de semnare al endpoint-ului (`whsec_…`), **afișat o singură dată**: păstrează-l acum.

`POST /webhooks/{webhook_id}/test` trimite un ping semnat și raportează rezultatul. `GET /webhooks/{webhook_id}/deliveries` listează livrările făcute către un endpoint, cele mai noi primele, iar `GET /webhooks/{webhook_id}/deliveries/{delivery_id}` fiecare încercare a uneia; livrările se păstrează 35 de zile.

## Verifică semnătura

Fiecare notificare este un `POST` cu trei anteturi:

| Antet | |
| - | - |
| `webhook-id` | Id-ul evenimentului. Același la fiecare reîncercare a aceluiași eveniment. |
| `webhook-timestamp` | Momentul semnării, în secunde Unix. |
| `webhook-signature` | Una sau mai multe valori `v1,<semnătură>` separate prin spațiu, unde semnătura este HMAC-SHA256 în base64 peste `<webhook-id>.<webhook-timestamp>.<corp>`, cu secretul drept cheie. |

Ca să o verifici:

1. Respinge un timestamp care diferă cu mai mult de **5 minute** de ceasul tău, ca o notificare interceptată să nu poată fi retrimisă mai târziu.
2. Calculează HMAC-SHA256 peste `id.timestamp.corp`, pe **corpul brut** exact cum a sosit, înainte de orice parsare JSON. Cheia este partea base64 a secretului de după `whsec_`, decodată.
3. Acceptă notificarea dacă se potrivește oricare valoare `v1,`, comparată în timp constant. Cât timp un secret este rotit, se trimit două semnături.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { createHmac, timingSafeEqual } from "node:crypto";

  const TOLERANCE_SECONDS = 5 * 60;

  // headers: nume cu litere mici, cum le dă Node; rawBody: corpul exact cum a sosit.
  export function verifyWebhook(secret, headers, rawBody) {
    const id = headers["webhook-id"];
    const timestamp = headers["webhook-timestamp"];
    const signatures = headers["webhook-signature"];
    if (!id || !/^\d+$/.test(timestamp ?? "") || !signatures) return false;
    if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;

    const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
    const expected = createHmac("sha256", key)
      .update(`${id}.${timestamp}.${rawBody}`)
      .digest();

    return signatures.split(" ").some((entry) => {
      const [version, signature] = entry.split(",");
      if (version !== "v1" || !signature) return false;
      const given = Buffer.from(signature, "base64");
      return given.length === expected.length && timingSafeEqual(given, expected);
    });
  }
  ```

  ```php PHP theme={null}
  <?php

  const TOLERANCE_SECONDS = 5 * 60;

  // $headers: nume cu litere mici; $rawBody: file_get_contents('php://input').
  function verifyWebhook(string $secret, array $headers, string $rawBody): bool
  {
      $id = $headers['webhook-id'] ?? '';
      $timestamp = $headers['webhook-timestamp'] ?? '';
      $signatures = $headers['webhook-signature'] ?? '';
      if ($id === '' || ! ctype_digit($timestamp) || $signatures === '') {
          return false;
      }
      if (abs(time() - (int) $timestamp) > TOLERANCE_SECONDS) {
          return false;
      }

      $key = base64_decode(preg_replace('/^whsec_/', '', $secret), true);
      $expected = base64_encode(hash_hmac('sha256', "{$id}.{$timestamp}.{$rawBody}", $key, true));

      foreach (explode(' ', $signatures) as $entry) {
          [$version, $signature] = array_pad(explode(',', $entry, 2), 2, '');
          if ($version === 'v1' && hash_equals($expected, $signature)) {
              return true;
          }
      }

      return false;
  }
  ```
</CodeGroup>

## Aplică o notificare

Corpul este evenimentul:

```json theme={null}
{
  "id": "evt_00000000000ab",
  "type": "property.updated",
  "occurred_at": "2026-10-16T09:30:00Z",
  "channel": "your-channel",
  "data": { "object": "property", "id": "prp_1qnykkykp69yv8d59hxk5410m9", "version": 42 }
}
```

* Livrarea este **cel puțin o dată**, în nicio ordine anume. Elimină duplicatele după `webhook-id`.
* Citește resursa numită de eveniment și aplic-o doar când `version`-ul ei este mai mare decât cel pe care îl ai.
* Un eveniment `availability.updated` numește o proprietate și un interval de nopți (`from`, `to` exclusiv) și nu are versiune: citește întotdeauna din nou acel interval.
* Răspunde cu orice `2xx` în cel mult **10 secunde** și fă treaba după aceea.

## Reîncercări și suspendare

Un răspuns non-`2xx` sau un timeout este reîncercat după 1 minut, 5 minute, 30 de minute, 2 ore și 6 ore, apoi la fiecare 12 ore, fiecare cu o variație de ±20 %, timp de **3 zile**. După aceea endpoint-ul devine `inactive` cu `suspended_reason: failing`, iar contactul tău tehnic primește un e-mail. Un răspuns `410 Gone` îl face `inactive` imediat (`gone`). O redirecționare nu este urmată niciodată și contează ca nelivrată.

La un endpoint inactiv nu se livrează nimic. `POST /webhooks/{webhook_id}/enable` îl reactivează după ce un ping semnat către el reușește. Nu se retrimite nimic: recuperează ce a ratat din `GET /events` ([Sincronizare](/ro/distribution-api/synchronisation)).

## Rotește un secret

`POST /webhooks/{webhook_id}/rotate-secret` emite un secret nou, afișat o singură dată. Cel anterior continuă să semneze alături de el 24 de ore (`previous_secret_expires_at`), așa că sosesc ambele semnături și un verificator care deține oricare dintre secrete acceptă notificarea; trimite `"expire_immediately": true` când secretul vechi a ajuns unde nu trebuia. Ștergerea unui endpoint îi anulează livrările în așteptare și îi distruge secretele.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.