inmake DOCS
Docs / API / Hooks (outbound)

Hooks (outbound)

The signed delivery feed: subscribing, event kinds, x-drop-* headers, seed verification.

Hooks are the platform's outbound feed: when a store's data changes, the backend POSTs the changed document to the endpoints the store subscribed — signed, self-describing, at-least-once. Render servers use it to keep caches warm; integrations use it for orders, invoices and form submissions.

Subscribing#

Hooks live on the store's setup/hooks document, edited in CRM & Studio under Settings › Advanced › Hooks. The standard form is one multi-event entry: an endpoint, a signing key and an events list.

events Meaning
['orders', 'invoices'] exactly those kinds
server the render-server group — data, files, templates, elements, setup, slug, services, bulk (never the PII kinds)
absent / empty every kind, current and future (CRM & Studio refuses to create a bare wildcard)

An entry has a type: callback (the default — POSTs the document) or notification (renders a store e-mail template and mails it to the entry's emails through the store's SMTP setup — or the platform mailer when the store has none; no endpoint, nothing signed). Both gate on enabled and on on: ['create' | 'update' | 'delete'] (absent = callbacks fire on all three, notifications on create only). Optional filter and mask shape what is sent.

Event kinds#

Kind Payload Seed label
orders the order document (masked) orders/{id}
invoices the invoice document (masked) invoices/{id}
data a localized data document (masked) {locale}/{collection}/{id}
forms a form record (masked) forms/{form}/{id}
files a file document files/{storage_path}
templates a signal {schemaId, version} templates/{schemaId}/v{n}
elements a signal {id} elements/{id}
setup a signal {id} — one of the public setup docs web / data / design / elements, or $store when the store's locales / name change setup/{id}
slug a signal {key, collection?, locale?} — a slug registry entry (root, or a collection's route registry) slug/{key} · slug/{collection}/{key}
bulk a signal {source: 'restore' | 'sync', job, parts, counts, at, backup?, env?} — many documents changed at once (a backup restore or an environment sync); no per-document push follows, so refetch everything you cache bulk/{job}

templates, elements, setup, slug and bulk exist only inside the server group: signals that tell a render server what to forget, never a document body. Deletes push an {id} tombstone with x-drop-deleted: true. Private setup documents (mail, hooks, analytics, shipping, …) and the carts collection are never pushed — not even as a signal. A data document's push carries its schema stamps (_dt / _f); see Caching & freshness.

Delivery headers#

Every callback carries x-drop-* headers so a receiver can dispatch without parsing the body:

Header Meaning
x-drop-event the kind (orders, data, …)
x-drop-updated the document's Firestore update time (epoch ms) — the ordering guard: drop a push that is not newer than what you hold
x-drop-locale, x-drop-collection, x-drop-id data context (id also on orders / invoices / forms / elements / setup / slug; collection + locale on slug)
x-drop-form · x-drop-schema + x-drop-version · x-drop-storage-path forms · templates · files
x-drop-deleted: true a tombstone
access-seed the signature (below)

Deliveries are awaited with a 10 s timeout per hook and not retried — answer quickly and reconcile from the API if you missed one.

Verifying a delivery#

When the hook has a private key, the access-seed header is <ts>.<signature> where signature = HMAC-SHA256(ts + '.' + label, key) and label is the per-push label from the table above, reconstructed from the x-drop-* headers. Reject old timestamps and mismatching signatures.

import {verifyHookSeed} from 'drop-core-sdk';

app.post('/hook', (req, res) => {
  const ok = verifyHookSeed(req.headers, process.env.HOOK_KEY!, {
    label: `${req.headers['x-drop-locale']}/${req.headers['x-drop-collection']}/${req.headers['x-drop-id']}`,
  });
  if (!ok) return res.status(401).end();
  // …apply req.body
  res.json({ok: true});
});

The SDK's Secure page documents verifyHookSeed; drop-core-server ships a complete receiver (WebSite.receiveEventHook) that verifies, applies the ordering guard and warms its caches.

A header named with an underscore (access_seed) is still sent for old receivers, but nginx drops underscore headers by default — verify access-seed.