Skip to main content
Webhooks deliver commerce events — a new order, a successful payment, a completed catalog sync — as they happen, in place of polling the API on a timer. An HTTPS endpoint is registered and subscribed to a set of events, and Galactic Core POSTs a signed envelope to it on every matching event. One endpoint runs through its whole life here: created, with its one-time secret captured; read for health; a delivery verified; tested when it succeeds and when it fails; auto-disabled after repeated failures; and finally handling order.paid end to end. All webhook management is secret-key only (sk), so these calls run on your server. A publishable key returns 403.

1. Registering an endpoint

An endpoint points at your handler URL and lists the events it subscribes to, or ["*"] for all of them. The response includes a signing_secret returned exactly once and never retrievable again — a lost secret is recovered by deleting the endpoint and re-creating it.
Response
The create response is the only one that ever includes signing_secret; getWebhookEndpoint and listWebhookEndpoints omit it, and there is no way to read it back. The mirror also holds: create carries no health fields and no delivery_stats, which appear only on get and list (step 2).

2. Listing and inspecting endpoints and their health

listWebhookEndpoints returns every endpoint with its delivery-health fields: last_success_at, last_failure_at, consecutive_failures, disabled_reason, and disabled_at. A freshly created endpoint carries them all at their zero state, null or 0.
Response
getWebhookEndpoint returns the same health fields and adds aggregate delivery_stats for a single endpoint:
Response
The field map across the two:

3. Health degradation and auto-disable

Failed deliveries are retried automatically with exponential backoff. Each failure bumps consecutive_failures and stamps last_failure_at; a success resets the counter. If an endpoint fails 20 deliveries in a row, Galactic Core disables it — enabled flips to false, a disabled_reason is stamped, and disabled_at is set. From then on it receives nothing. A disabled endpoint reads back like this:
Response
Patching enabled: true re-enables a recovered handler, which also resets the failure counter and clears disabled_reason:

4. The event envelope

Every delivery is a POST of the same envelope. data.object holds the resource that triggered the event, and the top-level fields describe it:
  • type — the event type, such as order.created.
  • livemodetrue for a live key, false for a test key. This is the field that keeps test events away from production systems.
  • api_version — the contract version (v1).
  • environmentproduction or sandbox.
An order.created envelope as it lands in a handler, taken from the event log:
When that order’s payment confirms, an order.paid envelope for the same order id follows, identical in shape and differing only in type:

5. Verifying the signature in your handler

Galactic Core signs every delivery and sends the signature in the X-Tybrite-Signature header as t=<timestamp>,v1=<hex hmac>. A handler recomputes the HMAC over `${t}.${rawBody}` with its stored signing_secret and compares in constant time before trusting the payload. The example below is handler code, so it names its own stack:
A handler that answers 200 immediately and processes asynchronously stays healthy under load. A slow one causes timeouts, which count as delivery failures and move the endpoint toward auto-disable (step 3).

6. Testing, auditing, and retrying deliveries

A test event exercises connectivity and the signature check end to end. Its payload carries test: true and placeholder data, so a handler treats it as a no-op rather than fulfilling or charging against it. The response reports the status_code the endpoint returned. A handler answering 200 makes the test succeed:
Response
An endpoint that is down or rejects the request returns the same call with success: false and the failing status_code. A run of these is what trips the auto-disable in step 3:
Response
The event log is where deliveries are debugged and history replayed from. It is reverse-chronological, cursor-paged, and filterable by type and environment:
Response
environment: 'sandbox' narrows the log to test-key events and 'production' to live ones — the same distinction the livemode flag carries on each envelope.
A manual re-delivery sends an event to every enabled, subscribed endpoint, logging each retry as a new attempt. retried counts the endpoints hit, and results[] reports each one’s outcome:
Response

7. Handling order.paid end to end

The order is not patched to paid from the webhook. When a payment confirms, Galactic Core has already marked the order paid, reduced stock, booked the sale, and updated the customer’s metrics. order.paid fires so that other systems can react to that — pushing the order into an ERP, sending a custom confirmation email, updating an analytics warehouse, notifying a fulfilment partner.

Supported event types

An endpoint subscribes to any of these, or to ["*"] for everything:
  • Order lifecycleorder.created, order.paid, order.fulfilled, order.shipped, order.cancelled, order.refunded, order.updated
  • Paymentpayment.succeeded, payment.failed, payment.refunded
  • Customercustomer.created, customer.updated, customer.deleted
  • Inventory & catalogproduct.created, product.updated, product.stock_low, product.out_of_stock
  • Cart & checkoutcart.created, cart.updated, cart.abandoned
  • Gift cardsgift_card.issued, gift_card.redeemed, gift_card.expired
  • Promotionspromotion.applied, promotion.created, promotion.activated (a promotion went live or was scheduled to), promotion.deactivated (a live promotion ended, was paused, or expired). A promotion awaiting approval is not yet a promotion: nothing fires while it is a draft, and promotion.created arrives when it is approved
  • Pricingpricing_rule.created, pricing_rule.activated (a dynamic-pricing rule went live), pricing_rule.updated (a live rule’s discount, scope, priority, or window changed, so the prices it produces are now different), pricing_rule.deactivated. As with promotions, a rule awaiting approval emits nothing until it goes live
  • Content & collectionscollection.created, collection.updated (name / banner / homepage placement changed, or membership changed — products added, removed, or re-ordered, carrying members_changed: true), post.published, lookbook.published, review.approved
  • Deletion & restoreproduct.deleted, product_variant.deleted, promotion.deleted, collection.deleted, post.deleted, lookbook.deleted, pricing_rule.deleted, subcategory.deleted, customer.deleted. The record has left the API: it is gone from every list, cannot be fetched by id, and stops taking effect — which makes each of these the moment anything built from that record, a page, a menu entry, a mirrored row, no longer has a source. Deleting is reversible for 90 days, so each event has a matching .restored and the payload carries restorable_until, leaving room either to remove a copy now and rebuild it if the restore arrives, or to wait the window out. Deleting a product also deletes its variants, so product.deleted is followed by a product_variant.deleted for each
  • Visibilitycategory.activated / category.deactivated, subcategory.activated / subcategory.deactivated. A category switched off is filtered out of every catalog read, so it leaves a storefront exactly as a deleted one would. Categories cannot be deleted — they are a fixed set a merchant enables or disables — so these are the only lifecycle events they emit, and the only signal available to navigation or any layout built from the taxonomy
  • Store lifecycle & configurationstore.updated (name / logo / branding / contact / base currency; carries changed_fields), payment_provider.connected, shipping_provider.connected, channel.connected
  • Feature availabilityfeature.status_changed (a capability crossed between available, awaiting_data, and not_in_plan; carries feature, status, previous_status)
  • Catalog sync & syndicationfeed.sync.completed (a scheduled inbound feed-pull finished — carries created/updated/failed counts), channel.sync.completed (a sales-channel push finished — carries pushed/rejected counts)
  • Wholesale (B2B)b2b.rfq.created, b2b.quote.sent / .accepted / .rejected, b2b.po.issued / .confirmed / .fulfilled, b2b.invoice.issued / .paid / .overdue (for stores with wholesale enabled)

Keeping a storefront in step with the store

The order, payment, and inventory events above track a transaction. The store lifecycle, content and collections, and feature availability events track the store itself changing, and they are what an automation tool listens to to keep a storefront current without anyone rebuilding it. A store rarely arrives fully formed. A merchant starts with a thin catalog and no reviews, no promotions, and no published content, leaving the matching storefront surfaces with nothing to show. As the store fills in, these events mark the moment each surface has something behind it:
  • promotion.activated carries the live promotion a banner and product badges render from, and promotion.deactivated marks when they stop applying.
  • review.approved is the point at which a product page has a real review behind its star summary and reviews block.
  • collection.updated covers a banner being added, show_on_homepage being turned on, and membership changing, the last carrying members_changed: true and calling for a re-read of the collection’s products. Membership churn is not only a manual edit: a merchant can set a collection to refresh itself on a schedule, so a shelf like “current best sellers” changes contents on its own. The event fires once per collection per change rather than once per product, so a ten-product rotation is one event.
  • pricing_rule.activated / .updated / .deactivated bound the validity of a cached price. A dynamic-pricing rule changes what every shopper pays without touching a single product record, so a cached price is stale from the moment one of these fires.
  • feature.status_changed (awaiting_data → available) is the moment a capability’s data exists — recommendations shelves once there is enough signal, a currency switcher once a second currency is configured. A not_in_plan → available transition is the same signal arriving after a plan upgrade unlocks the capability.
Anvil, Tybrite Labs’ autonomous storefront studio, builds its self-improving storefronts on exactly these events: it subscribes, and when one fires it proposes the matching storefront change for the merchant to preview and approve, without a rebuild or downtime. The same events are available to any tool.

What’s next

  • Pair feed.sync.completed with the inbound importer in Catalog Sync to close the loop on scheduled syncs.
  • See where order.paid comes from in the Storefront checkout flow.
  • See the full envelope shape and per-event payloads in the Webhooks reference.