Справочник

Уебкуки

Когато редактор публикува, скрие или изтрие запис, Citron изпраща кратко подписано съобщение до сайта ви, за да обнови той само страниците, които са се променили.

Настройване на уебкука#

  1. Добавете адресаВ конзолата отворете Настройки → Разработчици → Уебкуки и добавете адреса си, например https://example.com/api/citron-webhook. Той трябва да е публичен http(s) адрес.
  2. Изберете събития и колекцииИзберете някои от entry.published, entry.unpublished и entry.deleted. Оставете списъка с колекции празен, за да получавате за всички колекции.
  3. Запазете тайния ключКопирайте генерирания таен ключ (whsec_…) в средата на сайта си, например CITRON_WEBHOOK_SECRET. С него се подписва всяка доставка.

Събития#

СъбитиеКога се изпраща
entry.publishedЗапис е публикуван, включително при насрочено публикуване и при нова версия на публикуван запис.
entry.unpublishedПубликуван запис е свален от сайта.
entry.deletedПубликуван запис е изтрит. Изтриването на запис, който никога не е публикуван, не изпраща нищо, защото никоя страница не го е показвала.

Това са единствените три събития, които уебкуката получава. Новите видове събития ще се включват поотделно за всяка уебкука, така че съществуващ адрес никога не получава съобщение, което не е поискал.

Заявката#

Всяка доставка е POST с JSON тяло и три заглавки: content-type: application/json, x-citron-event (името на събитието) и x-citron-signature. Citron чака отговора ви до 10 секунди и не чете тялото му.

HTTP
POST /api/citron-webhook HTTP/1.1
content-type: application/json
x-citron-event: entry.published
x-citron-signature: sha256=a8db6ae35854b938a4493cfb12b30f5db059390af0a89e9057a10f19ed14fe99

{"event":"entry.published","organizationId":"cmf2x0r1k0000s6014h3j5k7l","collection":"products","entry":{"id":"cmf3k9x2p0004s601q8w7e2rt","slug":"bio-6"},"at":"2026-09-21T09:30:00.000Z"}
Съдържание
{
  "event": "entry.published",
  "organizationId": "cmf2x0r1k0000s6014h3j5k7l",
  "collection": "products",
  "entry": { "id": "cmf3k9x2p0004s601q8w7e2rt", "slug": "bio-6" },
  "at": "2026-09-21T09:30:00.000Z"
}
ПолеЗначение
eventИмето на събитието, същото като в x-citron-event.
organizationIdИдентификаторът на работното ви пространство.
collectionSlug на колекцията, както в /v1/content/{collection}.
entry{ id, slug } на записа. slug може да е null.
atКога е станала промяната (ISO 8601).

Ключовете и редът им са фиксирани: подписът се изчислява точно върху тези байтове, така че формата само ще се разширява, никога няма да се променя.

Проверка на подписа#

x-citron-signature е sha256=, последвано от HMAC-SHA256 на суровото тяло на заявката в шестнадесетичен вид, с тайния ключ на уебкуката. Изчислете го върху получените байтове, преди да разчетете JSON, и сравнявайте за постоянно време. Ако не съвпада, отговорете с 401.

Заглавката няма времеви печат. Полето at е покрито от подписа, така че можете да отхвърляте стари доставки по възраст; оставете щедър прозорец, защото доставките чакат на опашка, докато работният процес на Citron не е на линия.

import { constructWebhookEvent, WebhookVerificationError } from '@citron/sdk/webhooks';

// Any runtime with Request/Response: Node 18+, edge, Workers
export async function handleCitronWebhook(req: Request): Promise<Response> {
  const raw = await req.text(); // the RAW body: re-serialised JSON has different bytes
  try {
    const event = await constructWebhookEvent(raw, req.headers.get('x-citron-signature'), process.env.CITRON_WEBHOOK_SECRET!, {
      maxAgeSeconds: 24 * 3600, // optional; the signed "at" makes old deliveries refusable
    });
    console.log(event.event, event.collection, event.entry.slug);
    return new Response(null, { status: 204 });
  } catch (err) {
    if (err instanceof WebhookVerificationError) return new Response(null, { status: 401 });
    throw err;
  }
}

За да тествате обработчика си, подпишете тяло сами: signWebhookBody(body, secret) от @citron/sdk/webhooks връща стойността на заглавката. Примерът по-горе е подписан с тайния ключ whsec_3Jx9mQ2vR7tLp8Kd4Nw6Yb1Hs5Fc0Zeq.

Обновяване на Next.js сайт#

SDK съдържа готов route handler за App Router. Слагайте тагове на заявките си с citronTags(collection, slug) и при промяна на запис обработчикът обновява точно тези тагове. Страниците остават статични, докато редактор не публикува.

app/api/citron-webhook/route.ts
// app/api/citron-webhook/route.ts
import { revalidateTag } from 'next/cache';
import { citronTags, createRevalidateHandler } from '@citron/sdk/next';

// 503 without a secret, 401 for a bad signature, 500 if onEvent throws (the API retries)
export const POST = createRevalidateHandler({
  secret: process.env.CITRON_WEBHOOK_SECRET,
  onEvent: (e) => citronTags(e.collection, e.entry.slug).forEach((tag) => revalidateTag(tag)),
});
TypeScript SDK
import { citronTags } from '@citron/sdk/next';

// Tag the fetch so the webhook above can revalidate exactly this page:
// citronTags('products', 'bio-6') → ['citron:products', 'citron:products:bio-6']
const product = await citron.collection('products').find('bio-6', {
  fetchOptions: { next: { revalidate: 3600, tags: citronTags('products', 'bio-6') } },
});
console.log(product?.slug);

Повторни опити#

Всеки отговор 2xx се смята за доставка. При изтекло време, мрежова грешка или друг статус се правят още два опита: след 10 секунди и след още една минута. След третия опит доставката се изоставя. Адрес, който сочи към частна мрежа, се отказва и не се опитва отново.

  • Отговаряйте бързо и вършете бавната работа след това; обработчик, който отнема повече от 10 секунди, получава повторен опит, дори да е приключил.
  • Направете обработчика идемпотентен. Двойното обновяване на таг е безвредно.
  • Върнете 500, когато вашата работа се провали и искате нов опит; обработчикът от SDK прави това, когато onEvent хвърли грешка.

Справочникът описва само публичното API и следва договора му за съвместимост: публичните отговори само получават нови полета. Описанието на тези адреси във формат OpenAPI е на /developers/openapi.json.