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

> Registra un endpoint, verifica la firma Standard Webhooks di un avviso, scarta i duplicati e applicalo, e cosa succede quando il tuo endpoint non risponde.

<Warning>
  **Anteprima: non viene ancora inviato nulla.** Qui è descritto il contratto dell'API di distribuzione (versione `1.0.0`). Nessun avviso viene consegnato finché la sandbox non apre.
</Warning>

Un webhook è un avviso leggero: dice cosa è cambiato e lo nomina, e tu leggi la risorsa che nomina. Gli avvisi seguono [Standard Webhooks](https://www.standardwebhooks.com/), quindi una libreria per questo standard nel tuo linguaggio li può verificare.

## Registra un endpoint

`POST /webhooks` (permesso `manage:webhooks`) registra un endpoint, e il tuo canale ne può avere al massimo cinque:

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

* `url` è un indirizzo HTTPS pubblico sulla porta 443. Una destinazione privata, riservata, di loopback o link-local viene rifiutata (`url_not_allowed`), e i reindirizzamenti non vengono mai seguiti.
* `events` elenca fino a 20 tipi di evento o pattern (`*`, `property.*`, `availability.updated`), ognuno coperto dai permessi della tua chiave.
* La risposta contiene il `secret` di firma dell'endpoint (`whsec_…`), **mostrato una volta sola**: salvalo subito.

`POST /webhooks/{webhook_id}/test` invia un ping firmato e riporta l'esito. `GET /webhooks/{webhook_id}/deliveries` elenca le consegne fatte a un endpoint, dalla più recente, e `GET /webhooks/{webhook_id}/deliveries/{delivery_id}` ogni tentativo di una consegna; le consegne si conservano per 35 giorni.

## Verifica la firma

Ogni avviso è un `POST` con tre intestazioni:

| Intestazione | |
| - | - |
| `webhook-id` | L'id dell'evento. Lo stesso a ogni nuovo tentativo dello stesso evento. |
| `webhook-timestamp` | Quando è stato firmato, in secondi Unix. |
| `webhook-signature` | Uno o più `v1,<firma>` separati da spazi, dove la firma è l'HMAC-SHA256 in base64 di `<webhook-id>.<webhook-timestamp>.<corpo>`, con il segreto come chiave. |

Per verificarla:

1. Rifiuta un timestamp che si discosta di più di **5 minuti** dal tuo orologio, così un avviso intercettato non può essere rimandato più tardi.
2. Calcola l'HMAC-SHA256 di `id.timestamp.corpo` sul **corpo grezzo** così come è arrivato, prima di qualsiasi parsing JSON. La chiave è la parte base64 del segreto dopo `whsec_`, decodificata.
3. Accetta l'avviso se corrisponde almeno un valore `v1,`, confrontato in tempo costante. Durante la rotazione di un segreto arrivano due firme.

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

  const TOLERANCE_SECONDS = 5 * 60;

  // headers: nomi in minuscolo, come li dà Node; rawBody: il corpo così come è arrivato.
  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: nomi in minuscolo; $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>

## Applica un avviso

Il corpo è l'evento:

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

* La consegna avviene **almeno una volta**, senza un ordine preciso. Scarta i duplicati in base a `webhook-id`.
* Leggi la risorsa che l'evento nomina, e applicala solo quando la sua `version` è più alta di quella che hai.
* Un evento `availability.updated` nomina una struttura e un intervallo di notti (`from`, `to` escluso) e non ha versione: rileggi sempre quell'intervallo.
* Rispondi con un qualsiasi `2xx` entro **10 secondi**, e fai il lavoro dopo.

## Nuovi tentativi e sospensione

Una risposta non `2xx` o un timeout viene ritentato dopo 1 minuto, 5 minuti, 30 minuti, 2 ore e 6 ore, poi ogni 12 ore, ognuno con una variazione di ±20 %, per **3 giorni**. Dopo, l'endpoint diventa `inactive` con `suspended_reason: failing`, e il tuo referente tecnico riceve un'e-mail. Una risposta `410 Gone` lo rende `inactive` subito (`gone`). Un reindirizzamento non viene mai seguito e conta come non consegnato.

A un endpoint inattivo non viene consegnato nulla. `POST /webhooks/{webhook_id}/enable` lo riattiva dopo che un ping firmato verso di esso va a buon fine. Non viene rimandato niente: recupera quello che si è perso da `GET /events` ([Sincronizzazione](/it/distribution-api/synchronisation)).

## Ruota un segreto

`POST /webhooks/{webhook_id}/rotate-secret` rilascia un nuovo segreto, mostrato una volta sola. Quello precedente continua a firmare accanto al nuovo per 24 ore (`previous_secret_expires_at`), quindi arrivano entrambe le firme e un verificatore che ha l'uno o l'altro segreto accetta l'avviso; invia `"expire_immediately": true` quando il segreto vecchio è finito in mani sbagliate. Cancellare un endpoint annulla le sue consegne in attesa e distrugge i suoi segreti.


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