Αναφορά

Webhooks

Όταν ένας συντάκτης δημοσιεύει, αποσύρει ή διαγράφει μια εγγραφή, το Citron στέλνει ένα σύντομο υπογεγραμμένο μήνυμα στον ιστότοπό σας, ώστε να ανανεώσει μόνο τις σελίδες που άλλαξαν.

Ρύθμιση webhook#

  1. Προσθέστε τη διεύθυνσηΣτην κονσόλα, ανοίξτε τις Ρυθμίσεις → Προγραμματιστές → Webhooks και προσθέστε το τελικό σας σημείο, π.χ. 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Μια ζωντανή εγγραφή διαγράφεται. Η διαγραφή μιας εγγραφής που δεν δημοσιεύτηκε ποτέ δεν στέλνει τίποτα, αφού καμία σελίδα δεν την έδειξε.

Αυτά τα τρία είναι τα μόνα συμβάντα που λαμβάνει ένα webhook. Οι νέοι τύποι συμβάντων θα ενεργοποιούνται ξεχωριστά ανά webhook, ώστε ένα υπάρχον τελικό σημείο να μη λαμβάνει ποτέ μήνυμα που δεν ζήτησε.

Το αίτημα#

Κάθε αποστολή είναι ένα 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Το αναγνωριστικό του χώρου εργασίας σας.
collectionΤο slug της συλλογής, όπως στο /v1/content/{collection}.
entryΤο { id, slug } της εγγραφής. Το slug μπορεί να είναι null.
atΠότε έγινε η αλλαγή (ISO 8601).

Τα κλειδιά και η σειρά τους είναι σταθερά: η υπογραφή υπολογίζεται ακριβώς πάνω σε αυτά τα byte, οπότε η δομή μόνο θα επεκτείνεται, δεν θα αλλάξει ποτέ.

Επαλήθευση της υπογραφής#

Το x-citron-signature είναι το sha256= ακολουθούμενο από το δεκαεξαδικό HMAC-SHA256 του ακατέργαστου σώματος του αιτήματος, με κλειδί το μυστικό του webhook. Υπολογίστε το πάνω στα byte που λάβατε, πριν από οποιαδήποτε ανάλυση 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. Προσθέστε ετικέτες στα αιτήματα fetch με 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 και ακολουθεί τη σύμβαση συμβατότητας του API: οι δημόσιες αποκρίσεις μόνο αποκτούν νέα πεδία. Η περιγραφή OpenAPI αυτών των διαδρομών βρίσκεται στο /developers/openapi.json.