Webhook: un avviso firmato quando un documento viene creato o condiviso

Menu dell’account → Webhooks registra una URL che riceve un POST firmato per due eventi, document.created e document.shared. Si gestisce solo da una sessione autenticata, di proposito: resta fuori dall’API scriptabile, così una chiave API trapelata non può diventare un flusso permanente di tutti i documenti successivi.

In un account succedono due cose di cui un altro programma potrebbe voler sapere nell’istante in cui accadono: un documento è stato creato e un documento è stato condiviso. In Menu dell’account → Webhooks indichi una URL https:// che deve venirne a conoscenza e leggi il segreto con cui le consegne vengono firmate.

Che cosa arriva

Un POST il cui corpo JSON ha tre chiavi — l’evento, l’ora e i dati:

{
  "event": "document.created",
  "created_at": "2026-09-11T09:12:44.000Z",
  "data": { "id": "…", "name": "release-notes.md", "kind": "markdown-to-html", "size": 4193 }
}

document.shared porta id e name, il mode in cui il documento si trova ora, la url di condivisione e notified, gli indirizzi a cui un avviso è davvero partito. Nessuno dei due eventi porta il testo del documento: un destinatario che ne ha bisogno ha l’id e una chiave API.

Verificare la firma

Ogni consegna ha un header x-transformpipe-signature nella forma t=<secondi unix>,v1=<hex>, dove v1 è un HMAC-SHA256 sulla stringa <t>.<corpo>, con il segreto di questo webhook come chiave — la stessa forma usata da Stripe e GitHub, quindi al codice di verifica che hai già di solito basta un segreto diverso.

import { createHmac, timingSafeEqual } from 'node:crypto';

// `raw` è il corpo così come è arrivato. Una copia analizzata e ricodificata non dà lo stesso hash.
export function verify(raw, header, secret) {
  const [t, v1] = header.split(',').map((part) => part.split('=')[1]);
  const want = createHmac('sha256', secret).update(`${t}.${raw}`).digest('hex');

  return (
    v1.length === want.length &&
    timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(want, 'hex'))
  );
}

Rifiuta una t più vecchia di qualche minuto e una consegna intercettata non potrà esserti riproposta in seguito. Il segreto inizia con whsec_ e si può rileggere dalla finestra ogni volta che serve: a differenza di una chiave API, è mostrato da noi a te, quindi vederlo due volte mentre configuri un destinatario è legittimo e non una fuga.

Un tentativo, nessuna coda

Una consegna è una sola richiesta con cinque secondi di timeout. Non c’è un secondo tentativo né una coda: un destinatario fermo perde quell’evento, e l’evento successivo riprova da sé. La finestra mostra l’ultimo stato, o l’ultimo errore, per ogni URL. I reindirizzamenti non vengono seguiti, e l’indirizzo viene ricontrollato come indirizzo pubblico al momento dell’invio, non solo alla registrazione.

Perché una chiave API non può registrarne uno

I webhook si gestiscono solo da una sessione autenticata. Una chiave in grado di registrarne uno trasformerebbe una fuga puntuale in un flusso permanente di tutti i documenti successivi, cosa ben peggiore da perdere di una chiave.

Da leggere: convertire documenti con un’API e pubblicare da GitHub Actions.