Skip to main content

Custom Integrations — Extensions

Extensions let your own logic run inside Galactic Core’s own flows — reacting to store events, or standing in for a built-in integration at the seam where the platform calls one. Registration, signing, retries, revalidation and run logging are handled by the platform. (The other route to a Custom Integration runs your own service entirely outside the platform and drives it over the API: Bring Your Own Compute, for orchestration an Extension doesn’t cover.) If what you need is somewhere to record information the platform does not model, rather than logic to run, that is Custom Fields. With Extensions there are two things you can build:

Automations

React to a store event — order.paid, product.stock_low, cart.abandoned — with a no-code recipe or a small custom function. “When X happens, call my system / sync a field / send a message.”

Custom Providers

Become the implementation Galactic Core calls for a capability — Email, SMS, Marketing, Tax, Shipping rates, Sales channels, Payments, or shopper sign-in — standing in for the built-in integration.
Where extensions live: you create and manage extensions in your Tybrite dashboard under Integrations → Extensions — not through the storefront API. A recipe builder, a code editor, a remote-endpoint registration form, and a live run-log debugger are all there.
Built-in integrations stay the default. A custom provider only takes over a capability once you explicitly select it for your store; until then, everything runs exactly as it does today.

The two building blocks

Automations

An automation runs after the fact, off a real store event, and never blocks anything. You author it two ways:
  • A recipe — a small, validated JSON graph (trigger → filter → transform → action) with no raw code. Ideal for the “notify my system / call an API on order.paid” case.
  • A custom function — a small handler when you need real logic.

Custom providers

A custom provider — one you build and connect yourself — implements one of these eight named interfaces, and when selected is dispatched at that subsystem’s seam in place of the built-in path. (Advertising and Accounting sync are also provider-backed, but Galactic Core builds and runs those for you — see the note below the table — so they are not in this build-your-own set.)
For single-active interfaces (Email, SMS, Tax, Marketing, Auth) selecting a custom provider replaces the built-in for that capability. Shipping adds a rate source next to the native ones; Sales channels are fully multi-active; Payments route per transaction, so your custom method runs alongside the native processors.
AuthProvider is remote-only and fail-closed. It verifies who a shopper is, so it must run as a remote endpoint you host, must be synchronous, and if it rejects a token or can’t be reached the request is treated as signed-out — never signed-in. It is the second of the two bring-your-own-auth modes: with the first, your backend verifies the shopper and signs an assertion Galactic Core checks (x-external-auth); with an AuthProvider, the shopper’s raw identity-provider token is sent to your verifier and Galactic Core trusts the identity it returns (x-idp-token). See Customers and Auth.
Advertising and Accounting sync are platform-managed integrations — you connect them rather than build them. Advertising runs your Google/Meta ad campaigns (see Advertising); Accounting sync mirrors every posted ledger entry into QuickBooks so a store’s external books stay current automatically (see Accounting).

Execution modes: hosted vs remote

Every custom function and provider runs in one of two modes. The contract is identical — only where the code runs differs.

Hosted

You write a handler; Galactic Core runs it in an isolated sandbox. Your provider secrets are held in our secure vault and injected at run time. Nothing to deploy or operate.

Remote

You run an HTTPS endpoint on your own infrastructure. Galactic Core calls it with a signed request and expects the same return shape. Your provider secrets never leave your side.

Hosted handler

A hosted function is a single default-export handler. Galactic Core passes it the event (or, for sync hooks, the hook input) and a small, safe ctx:
The ctx surface is deliberately tiny — it’s the entire capability set: There is no database client, no API key, and no access to any other store’s data in ctx. If your function needs to change store data, it does so the same way any client does — ctx.fetch to the public API with your own key (supplied as a secret alias).

Remote endpoint

In remote mode, Galactic Core POSTs the same input a hosted handler would receive to your registered endpoint_url, and your endpoint returns the same shape a hosted handler would return. The request is signed so you can verify it came from Galactic Core: Three details govern whether verification succeeds:
  • The signed input is the raw header string and the raw body bytes. Re-serialising the parsed JSON changes key order and spacing, and the signature will not match.
  • X-Timestamp is in milliseconds, so it compares directly against Date.now().
  • An absent signature is not an exemption. Galactic Core sends no X-Signature at all when an extension has no signing secret configured, so an endpoint that verifies only when the header is present accepts anything from a caller that omits it.
A verification pass ahead of any handling:
Timestamp / replay: requests carry a 5-minute freshness window — reject anything older. Retries: on a transient failure (a network error or a 5xx), Galactic Core retries the request once, re-signing with a fresh timestamp; a 4xx is treated as final and is not retried. Your endpoint_url must be HTTPS and publicly reachable — internal, link-local, and metadata addresses are rejected.

Receiving callbacks from a provider

Some providers do not answer immediately. A mobile-money charge, a bank transfer, or an identity check finishes later and notifies you by posting to a URL. Every extension has one for this:
Generate it in Integrations → Extensions on the extension itself, then give it to your provider as their webhook or callback URL. The token appears once when you generate it and is stored only as a hash, so a lost URL is replaced by generating a new one rather than looked up — generating again immediately invalidates the previous URL, which is also how you revoke one that has leaked. When a provider posts to it, Galactic Core authenticates the request, then invokes your extension with the provider’s payload as input.callback and operation set to callback. What happens next depends on your execution mode:
  • Remote — the payload is forwarded to your endpoint_url as a signed Galactic Core dispatch, with the same headers and verification rules as any other remote call. Your endpoint verifies a request from Galactic Core rather than an unauthenticated one from the provider.
  • Hosted — your handler is invoked with the payload directly.

Choosing how the callback is authenticated

A public URL that runs your code needs to prove who is calling it, and providers differ in what they can offer. Three modes, configured per extension: With hmac, the signature is verified against the raw body before your code runs, and a request arriving without the header is rejected rather than allowed through — the same rule that governs remote dispatch in reverse. Bare digests, sha256=…, and t=…,v1=… header formats are all accepted. Every rejected callback is recorded in the extension’s run log with the reason, so a provider misconfiguration is visible rather than silent.
Acknowledgement is immediate. The endpoint answers as soon as the request is authenticated and recorded, before your handler finishes. Providers re-deliver when a response is slow, so waiting on your code would invite duplicate deliveries. Your handler’s return value is applied through the normal provider contract, and money-bearing results are revalidated server-side — a callback asserting that a payment succeeded does not by itself settle an order.Limits: 120 callbacks per minute per extension, and a request body up to 128 KB. A callback counts as one invocation.

Provider contracts

Each interface operation receives an input (with an operation discriminator where it has more than one) and must return the shape below. This is the same whether you run hosted or remote. Money-bearing returns (payment status, rate amounts) are revalidated server-side — see Safety.
How this relates to the storefront API. Connecting a custom provider does not change the storefront API contract your app calls. A shopper’s storefront never sees “which provider handled this” — the provider is invoked internally behind the endpoints you already use:
  • Payments — you still call POST /v1/payments/initialize / verify exactly as before. The provider field already accepts any name, and the response shape is unchanged, so a custom payment provider just means a provider name outside the built-in set is handled instead of rejected. (The platform still validates the amount and re-checks the paid status.)
  • Tax — resolved server-side; it only affects the order’s tax_amount, which is already part of the order response.
  • Shipping — the one visible addition: GET /v1/shipping/calculate may return extra rates[] from your custom carrier and set rate_source to custom (see calculateShipping).
  • Email / SMS / Marketing / Sales channels — entirely off the storefront API (notification and sync internals); there’s nothing to call.
So the provider contracts above describe what your extension implements, not what the storefront API returns. The only storefront-API change in the whole feature is the shipping rate_source: custom value.

Billing your own merchants

SubscriptionBillingProvider is the one interface that is not about a shopper. It is how an agency running its own commerce platform charges its merchants for their plan — the merchant pays the agency, on the agency’s own payment account, and Galactic Core never touches that money. Stripe, Lemon Squeezy and Paddle are supported directly, and Paystack in its markets, so an agency using one of those connects it in their portal rather than building anything. A SubscriptionBillingProvider covers the remaining cases — a regional gateway outside that set, or an agency’s own billing system that already holds the customer relationship.
This is a different money flow from PaymentProvider, which is a shopper paying a merchant at checkout. Subscription billing is a merchant paying their agency for the platform. They use separate interfaces so the two can never be resolved through each other’s seam: a shopper’s checkout is never affected by an agency’s billing choice, and vice versa.
Three operations, all admin-plane — no shopper is ever waiting on them:
amount_cents is server-authoritative. It is resolved from the agency’s own price catalogue, not supplied by the browser. Charging a different amount does not change what the merchant is recorded as owing — it only creates a mismatch between your gateway and the platform’s ledger.

Triggers

Automations subscribe to the same event vocabulary as webhooks. The events that fire today:

Orders

order.created · order.paid · order.fulfilled · order.shipped · order.cancelled · order.updated · order.refunded

Payments

payment.succeeded · payment.failed · payment.refunded

Customers

customer.created · customer.updated

Products

product.created · product.updated · product.stock_low · product.out_of_stock

Gift cards

gift_card.issued · gift_card.redeemed · gift_card.expired

Carts & sync

cart.abandoned · feed.sync.completed · channel.sync.completed

Promotions

promotion.applied · promotion.created · promotion.activated · promotion.deactivated · pricing_rule.created · pricing_rule.activated · pricing_rule.updated · pricing_rule.deactivated

Content & collections

collection.created · collection.updated · post.published · lookbook.published · review.approved

Deletion & restore

product.deleted · promotion.deleted · collection.deleted · post.deleted · lookbook.deleted · pricing_rule.deleted · subcategory.deleted · customer.deleted — the record has left the API. Each has a matching .restored, since deleting is reversible for 90 days; the payload carries restorable_until.

Visibility

category.activated · category.deactivated · subcategory.activated · subcategory.deactivated — a category switched off is filtered out of every catalog read, so it leaves a storefront just as a deleted one would.

Store lifecycle

store.updated · payment_provider.connected · shipping_provider.connected · channel.connected

Feature availability

feature.status_changed (a capability crossed between available / awaiting_data / not_in_plan)
The store-lifecycle, content, and feature-availability triggers fire as the store grows rather than per transaction, so an automation can react to a promotion going live, a review being approved, or a licensed capability gaining its first data. Each event’s payload is documented in the Webhooks reference.

Safety

Custom code sits on paths a sale runs through, so the platform bounds what a failure in it can reach:

Automations never block

Automations run after the event that triggered them. A slow or failing automation can never delay or block checkout, an order, or any request.

Sync providers fail safely

A ShippingProvider or TaxProvider that errors or times out falls back to the native path — the sale continues. A PaymentProvider failure returns provider unavailable with no order created or changed — never a false “paid”.

Opt-in gates

order.validate is fail-open by default (a buggy rule never blocks sales). You can opt a specific gate into fail-closed when you need a hard compliance stop.

Galactic Core stays the authority

A custom provider’s claimed payment status and returned rate amounts are revalidated server-side and never trusted as final. The platform remains the price, payment, and ledger authority.
Every extension is also protected by a per-extension circuit breaker — after too many consecutive failures it is automatically disabled and you’re notified — and hard timeouts on any hot-path hook. Outbound requests from a hosted function are filtered to public destinations to prevent them reaching internal infrastructure.

Budgets

Each budget below is a ceiling for its class. A timeout_ms on your extension can only make its budget smaller, never larger, so an endpoint written against a longer budget is cut off at the limit for the class it runs in. A shopper-facing hook runs while a page render waits on it, which is what sets those budgets in hundreds of milliseconds. The admin-plane budget is larger because a merchant has clicked a button and the work happens in front of them: a billing provider makes two round trips, authenticating then creating, which does not fit inside a checkout budget.
These ceilings apply to every individual call, so an endpoint’s slowest path is the one that determines whether it fits. A lookup for a reference the provider has never seen is often far slower than a normal call, and an endpoint sitting just under the ceiling on average fails intermittently, which surfaces as flakiness rather than as a misconfiguration.

Code review

Stored handler source is scanned on a schedule, and on demand from the Extensions page. The scan reads the code as text — it does not run it — and separates what it finds into two kinds. Corrected automatically. A fault with a single correct answer is repaired in place, because the exposure grows for as long as it stands. A provider key written directly into source is replaced with a placeholder, and a request addressed to an unencrypted http:// endpoint is moved to https://. Each change keeps a before-and-after you can read and undo, and the stored before-image has the credential stripped out of it, so the record of the fix does not preserve the thing it removed. A redacted key means the handler will now fail to authenticate rather than continue running on an exposed credential — connect it under Integrations and read it by name. Reported for you to decide. Anything where a reasonable author could disagree is left alone and raised as a suggestion, since a rewrite changes what the extension does. The most common one is reading a connected credential as ctx.secrets['name'] rather than await ctx.secrets.get('name'): the first form returns nothing, so the request goes out unauthenticated and the provider’s refusal looks like an account problem rather than a bug. Scanning and automatic correction are separate settings. Turning correction off keeps the scan and reports everything instead, including the faults that would otherwise be repaired. Where a store is managed by an agency, the agency decides whether its merchants are scanned.
The scan reasons about text, so it recognises the patterns above rather than understanding your logic. It is a floor under the obvious mistakes, not a substitute for reviewing what an extension does with the data it touches.

Examples


Choosing your approach