Store settingsWebhooks

Webhooks

Have Brainerce notify your own systems when orders, customers, inventory or payments change, resend events that were missed or failed, and call a third-party API such as a voucher or licence service.

A webhook is your store telling an outside system that something happened. You give Brainerce an HTTPS endpoint and a list of events; when one of those events occurs, we POST it to your URL.

This is the opposite direction from an API key, which is an outside system asking your store for something. Most integrations need both.

The Webhooks page: endpoints, Event log and Settings

Who can use it. Opening the page needs View webhooks; creating, editing, pausing, rotating, deleting, sending test events and resending need Manage webhooks. No role has either by default except the Owner: a Manager does not see Webhooks until you tick the boxes on that member. See Team and permissions.

Settings → Webhooks shows an Endpoints table, one row per endpoint. Above it a summary of the last 24 hours shows how many endpoints are active, how many events were Sent in 24 hours (and how many failed), the Success rate, and Waiting to be resent, which counts events that were not sent while an endpoint was stopped (click it to open that endpoint's log already filtered to them). You can filter the table by All, Active, Needs attention or Paused, and search by URL or description.

The summary also shows the average response time under Success rate and a 7 day sparkline of sent and failed events. Needs attention in the filter means an endpoint that is stopped automatically or has events waiting to be resent.

Each row shows the Status (Active, Paused or Stopped automatically), the Endpoint URL and description, the Events it listens to (the first two as chips, then "+N more"), Success rate (all time) (sent out of all attempts, or "No data"), Last activity (or "Never"), a Custom setup column that says Default or shows how many headers and whether a custom body is set, and the Actions: two buttons, with a small number on Event log when events are waiting to be resent. Clicking anywhere on the row also opens the event log.

  • Event log opens the list of everything sent to that endpoint: what succeeded, what failed, and what was Not sent because the endpoint was stopped. This is where you resend events.
  • Settings opens the endpoint's settings, in sections you can jump between: General, Events, Custom headers, Message body, Security (the signing secret and Send test event) and Delete.

The More actions menu on a row holds Pause (or Activate), Rotate signing secret and Delete.

Create an endpoint

  1. Go to Settings → Webhooks.
  2. Click New Endpoint.
  3. Enter the Endpoint URL. It must start with https://. A plain http:// address is rejected when you save, with an error rather than a warning. It must also be a public address on the internet: localhost, 127.0.0.1, and internal/private network addresses (anything on 10.x, 192.168.x, 172.16-31.x, or 169.254.x) are refused for the same reason, so you cannot point a webhook at a machine on your own office network. If the save fails on this step, the URL is the reason. Temporary inspection sites such as webhook.site, requestbin.com and requestbin.net are refused too: they expire, and an expired address answers every event with a 404. For a local test, use a tunnel such as ngrok or smee.io that forwards to your own handler. The URL can be up to 2048 characters.
  4. Add a Description (optional, up to 200 characters) so you can tell endpoints apart later ("Production order handler").
  5. Pick the events you want (see below). At least one is required. The window counts them ("3 selected"). Choose only the ones you'll act on; every extra event is traffic your endpoint has to absorb.
  6. Click Create Endpoint, and save the signing secret immediately.

Custom headers and a custom message body are optional. In the create window they sit under Advanced: headers and message body, and in the endpoint's Settings afterwards. You only need them to call a third-party API directly (see Call a third-party API). The live preview of a custom body is only available in Settings after the endpoint exists.

Which events can you pick

Events are grouped as Orders, Customers, Products, Inventory, Checkout, Payments, Donations, Returns, Stock transfers and Blog, each with a one-line description. Among them: order.created, order.updated, order.paid, order.fulfilled, order.cancelled, order.refunded, customer.created, customer.updated, customer.deleted, product.created, product.updated, product.deleted, inventory.updated, inventory.low, checkout.completed, checkout.abandoned, payment.succeeded, payment.failed, payment.refunded, blog.post.published, blog.post.updated, donation.paid, donation.refunded, return.requested, return.approved, return.declined, return.received, return.completed, return.cancelled, transfer.created, transfer.shipped, transfer.received and transfer.cancelled.

  • order.created is also sent for orders that are not paid yet. To issue vouchers or licences, choose order.paid only.
  • A donation is not an order. donation.paid fires for a donation, and order.paid is never sent for one.
  • The return.* events tell you where a return stands, and the money has its own event. Closing a return that refunds the customer sends return.completed and order.refunded, and the amount is only on order.refunded. A return that is an exchange sends return.completed and no order.refunded, because no money moved. Subscribe to order.refunded if you reconcile refunds, and to the return.* events if you track returned stock or run your own returns process.
  • A return that starts approved sends return.requested and then return.approved, almost at once. That happens when you open the return yourself, or when your return rules approve requests automatically. return.requested does not mean the return is waiting for you, so read the status on the event. The event carries the items and the customer's note, but never your private notes on the return, your own reason for declining, or which team member handled it.
  • The transfer.* events tell you which warehouse stock moved between, and inventory.updated tells you the store-wide number. A stock transfer sends one event per step: transfer.created (a draft was saved), transfer.shipped, transfer.received and transfer.cancelled. Editing a draft sends nothing. Each carries the two warehouses, the transfer number and its lines. Receiving in parts sends one transfer.received per receipt: the lines show the totals so far and what this receipt added, so your system can count each receipt once. Shipping, accepting units and canceling a transfer in transit also send inventory.updated, because the store's on-hand stock changes; a draft and a receipt that only rejects units do not. The instant Transfer Stock button on the Warehouses page has no document and sends neither event. A purchase order sends none of these.
  • checkout.completed is, for now, the same event as a new order. It fires together with order.created, so subscribe to one of them.
  • checkout.abandoned is narrower than it sounds. It fires only for a logged-in customer's cart that sat idle for an hour without ever reaching checkout, and only when your store has Also recover abandoned carts (logged-in customers) turned on. Guest carts, and a checkout that was started and left, do not send it.
  • A sale sends inventory.updated when the order is created, for a card payment too. A shopper who only has the item in a cart or in a checkout in progress sends nothing, and neither does that hold expiring. An order with several items sends one event per product (or per variant).
  • inventory.low is never sent when stock reaches zero, and at most once per product, per variant and per warehouse every 24 hours. If you set a low-stock number on a warehouse, the event also fires for that warehouse alone (it then carries warehouseId and warehouseName), even when the store-wide total is healthy. For out-of-stock, listen to inventory.updated.
  • For a product with variants, read variantInventory on inventory.updated, not inventory. The event says which variant changed (variantId) and carries that variant's own counters in variantInventory. The inventory block next to it is the product-level row, which stays at zero when the stock is kept per variant, so a number read from there looks like "out of stock" while the variants still have units. Both blocks include the units you hold back (unavailable), which are on hand but not for sale.
  • inventory.updated also carries incoming: the units on purchase orders and in transfers still on their way. It is the same number as the Incoming column on the inventory screen, and it is 0 when nothing is on the way. It is a snapshot of when the event was sent, not a live figure: placing a purchase order sends no inventory.updated, because those units are not in your stock yet. The event fires when the units are received, and incoming drops by what arrived. Shipping a stock transfer does send it, because the units leave your on-hand stock: available goes down and the shipped units show in incoming. incoming is never part of available.
  • product.updated and product.deleted are sent for a single product edit or delete only. Bulk price, status or description edits, variant edits, a bulk delete and catalog imports send nothing.
  • customer.updated is sent when a customer's profile is edited, not when Brainerce itself changes the customer (order totals, marketing opt-in or unsubscribe, email verification, password changes).
  • order.updated is sent for a status change, a shipping address edit or marking an order as paid, not for notes, tracking numbers, custom field values or item edits.
  • Events can arrive out of order. order.paid can reach you before order.created; use the createdAt on the event to order them.

When customer.created fires: shopper signups now count

customer.created fires every time a new customer account appears, including when a shopper signs up on your storefront by themselves. That covers a normal storefront signup, a guest checkout that gets upgraded to a real account, a signup that went through email verification, and a "Sign in with Google"-style social signup, as well as a customer you add by hand in the dashboard.

Size your handler for one event per storefront signup, not only for customers your team adds by hand.

Guest checkout does not fire it. A shopper who buys without registering gets a customer record, but no account and therefore no customer.created, so a CRM built only on this event will silently miss every guest. You still get those people on the order events: order.created and checkout.completed normally carry the customer details. Occasionally an order event arrives with only the order ID and no order details, so your system should look the order up by that ID instead of skipping the event. If your goal is "every buyer reaches my CRM", subscribe to an order event too, not just this one.

The same customer can trigger it more than once. A guest who bought months ago and now registers, or a customer you created by hand who later signs up on the storefront with the same email, both produce a customer.created carrying the customer id you already have. Treat the event as "create or update this customer id", never a blind create, or you will end up with duplicates.

The event carries the customer record. data holds the customerId and a customer object with the email, phone, name, tags, metadata, marketing consent and order totals. It does not carry the customer's IP address, the referral code they signed up with, or any password data. Because this is personal data sent to your URL, only subscribe an endpoint you control. The customer object is best effort: if it cannot be loaded, the event arrives with the customerId alone, so look the customer up by that id.

There is no separate "customer registered" event to subscribe to. If you are looking for one to tell a self-signup apart from a dashboard-created customer, it does not exist and cannot be added from this page: both arrive as customer.created and look identical. If that distinction matters to your system, record it on your own side at the point the shopper signs up.

The signing secret, and how to verify a webhook signature

You are shown the signing secret once, at creation. Save it before closing the dialog.

Use it to verify the HMAC-SHA256 signature on every webhook you receive. This matters more than it might sound: your endpoint is a public URL, so anyone who finds it can POST to it. Checking the signature is what tells you a request genuinely came from Brainerce and not from someone who guessed your URL.

How the check works (for whoever writes the code that receives the webhook):

  • Every delivery carries an X-Brainerce-Signature header and an X-Brainerce-Timestamp header (Unix time in milliseconds).
  • The signature is the hex-encoded HMAC-SHA256, keyed with your signing secret, of the timestamp, then a dot, then the raw request body exactly as received: ${timestamp}.${rawBody}. Hash the raw text, not a parsed and re-written copy of the JSON.
  • Compare in constant time, and reject a request whose timestamp is more than about 5 minutes old, so a captured request cannot be replayed.
  • Brainerce does not check the signature for you. Your endpoint has to. A webhook receiver that skips this check accepts forged events.

Ready-made code in Node, Python and Go is in the developer recipe Verify a webhook signature.

If you lose the secret, use Rotate signing secret (in the row's More actions menu, or in the endpoint's Settings under Security) to issue a new one. You'll see the new value once, on the same terms. Rotating invalidates the old secret, so update your endpoint promptly.

Watching deliveries in the Event log, and how many times we retry

Open an endpoint's Event log to see its delivery record: the status of each one (Sent, Failed, Not sent, Resent, Pending), the HTTP code, response time, number of attempts and when. You can filter by status (All, Sent, Failed, Not sent, each with its count) and by event type (only the endpoint's own events are listed), use the refresh button, move through 25 rows per page with Previous and Next, and click a row to expand it: What was sent, the Original event and the Response. To test, open the endpoint's Settings, go to Security and click Send test event, pick an event type (only events the endpoint listens to are listed), then check the Event log a few seconds later. A test needs an active endpoint that has not been stopped and listens to at least one event.

Send test event sends a brand-new test event. It does not re-send a delivery that already failed. To send a failed event again, use the resend described below. You can send 10 test events an hour per endpoint; past that the page says "Too many test events" until the hour is up. A test event carries only the ID fields (for example orderId) plus _test: true, never the full order or customer, and its event id starts with evt_test_. A test delivery is tried 5 times and counts toward the automatic stop like any other.

An endpoint with custom headers or a custom body cannot be tested with a plain test event. A test event to a third-party API is a real call: that service cannot tell it from a real order and may act on it (issue a voucher, charge an account). The window says This is a real call. and offers Open preview or Send anyway. Use the preview in the Message body section to see what would be sent without sending it. The test uses the saved settings, not unsaved edits. The test event's data carries _test: true, so your handler can skip real side effects.

Any answer that is not a 2xx counts as a failure, including a redirect (301 and 302 are not followed, so use the final URL), 404 and 410. There is no special handling of 410 Gone, and Brainerce never turns an endpoint off by itself except through the automatic stop described below. Waiting more than 10 seconds for an answer is a failure too. Each event is tried 5 times over about 75 seconds, then given up on. The delays are 5, 10, 20 and 40 seconds after the first attempt. If your endpoint is down for longer than about a minute and a quarter, that event is marked Failed in the Event log. A failed event is not sent again by itself. Nothing re-queues it later; you resend it yourself from the Event log (see Resend missed or failed events).

Active, Paused, Stopped automatically: what each status means

StatusWhat it means
ActiveEvents are being sent to the endpoint.
PausedYou turned the endpoint off (Pause). Nothing is sent and the endpoint keeps its settings and secret.
Stopped automaticallyBrainerce stopped sending because your endpoint kept failing. This is the "circuit breaker" in other docs.

What happens to events while an endpoint is stopped: they are kept for 7 days

If your endpoint fails repeatedly, Brainerce stops sending and the endpoint shows Stopped automatically. It takes roughly two failing events in a row to get there, not ten or twenty: each event is retried 5 times, and 10 failed tries stops it.

This is a protection, not a punishment: it stops a dead endpoint from absorbing retries indefinitely. But it also means your integration is now receiving nothing, so it is worth checking this page when something downstream seems stale.

Events that happen while the endpoint is stopped are not lost. Each one is recorded in the endpoint's Event log as Not sent, with its full content. Brainerce keeps up to 500 per endpoint, for 7 days: the waiting count, the badge and Send # missed events only count the last 7 days. After you fix the endpoint you resend them from the Event log. Failed events are different: they stay in the log and can be resent at any time, with no 7 day limit.

Brainerce never resends them by itself. There is no automatic replay. If you do nothing, they stay as Not sent until they expire after 7 days. Anything past 500 events, or older than 7 days, was not kept, so re-import that period from your Orders or Customers list instead.

Does Stopped automatically fix itself? After an hour, but it never sends the missed events

Yes, eventually, and only for new events.

An hour after the endpoint stops, Brainerce tries again with the next event that comes in (and any others that arrive in the few seconds before that first one gets an answer, so expect a couple of deliveries, not exactly one). If your endpoint answers normally, it goes back to Active and deliveries resume on their own. If it fails again, it stays stopped for another hour, and because the failure count is not reset, a single failed delivery is enough to stop it again. A broken endpoint therefore stays broken, retried about once an hour. Resending a single event while the endpoint is stopped follows the same rule: if that resend fails, the hour starts over.

The Not sent events from that hour are still waiting. Recovery restarts delivery from that moment onward. It does not resend the Not sent events. You resend them yourself.

Don't wait for the hour if you have already fixed the endpoint. Fix it, then open it and press Restart (the button at the top of the endpoint window, shown only while it is stopped). Restart turns the endpoint back on and resets the failure count, so deliveries resume immediately rather than an hour later, and on an endpoint that isn't one failure away from stopping again. Pause and then Activate from the row's More actions menu does the same.

Resend missed or failed events

Open the endpoint's Event log. While the endpoint is stopped, a banner says "This Endpoint was stopped automatically" and how many events were not sent and saved; press Restart first, then use Send # missed events. You can resend:

  • Everything that was Not sent. Filter the log to Not sent and resend them (the button reads Send the missed event or Send # missed events). They go oldest first, up to 25 at a time, a half second apart. Repeat until none are left. This only works once the endpoint is Active and no longer stopped, so fix the endpoint and re-enable it first. You can do this 3 times an hour per endpoint.
  • Chosen events. Tick one or more rows (up to 25; the tick box in the header selects the first 25 on the page) and press Send selected again, or press Send on a single row. This also works on Failed events, not only Not sent ones. Resending a single event works even while the endpoint is stopped, which is handy to test a fix. Resending several at once is refused while the endpoint is stopped. You can do this 30 times an hour per endpoint.

What a resent event looks like to your system:

  • It has the same event id as the original, so a system that skips events it has already processed will recognise it.
  • It carries an extra header, X-Brainerce-Redelivery: true, so you can tell it was resent.
  • It is signed again, and it uses the endpoint's current custom headers and custom body, not the ones from when the event first happened.
  • It is tried once. There are no retries. If it fails, it shows as Failed and you can resend it again.
  • Test events cannot be resent. Only real events can.

Resending can create a duplicate on the other side. The event id protects your own system, but a third-party API behind a custom body (a voucher or licence service) never sees it. Put the order id in the custom body (for example as ref) so the other side can refuse a second request for the same order. If you resend an order whose voucher was already issued and the other side does not check, you may issue it twice.

Call a third-party API: custom headers and custom body

By default Brainerce sends the event as it is (Body mode: Full event). Some services, such as a voucher or licence provider, expect an API key in a header and a different request body. You can set both on the endpoint, in the Custom headers and Message body sections of its Settings, so you do not need your own server in the middle. In Message body, choose Custom template to write your own.

Custom headers (for an API key)

  • Add up to 10 headers, each a name and a value. Names use letters, digits and hyphens. Values are plain printable text, up to 2048 characters.
  • They are sent with every request, including resends.
  • Values are stored encrypted and can never be seen again, not by you, not through the API, not by the AI assistant. You see only the header names. To change a value, enter the name again with the new value. To remove a header, remove it from the list.
  • You cannot set these headers: Host, Content-Length, Content-Type, Transfer-Encoding, Connection, Upgrade, TE, Trailer, Expect, Cookie, Set-Cookie, Keep-Alive, Accept-Encoding, anything starting with Proxy-, or anything starting with X-Brainerce-. Those are controlled by Brainerce.
  • Only you can set them, in the dashboard (or the API). The AI assistant and the connected AI tools cannot set or read header values, the same rule as the signing secret.

Custom body (a template)

Write the JSON you want the other service to receive. Inside text values, use {{...}} to insert details from the event. For example, for a paid order:

{
  "amount": "{{data.order.totalAmount}}",
  "currency": "{{data.order.currency}}",
  "ref": "{{data.orderId}}",
  "productRef": "{{data.order.items.0.sku}}"
}
  • A value that is only a placeholder keeps its type. A number stays a number, an object stays an object, and something that is not in the event becomes null.
  • A placeholder inside longer text is inserted as text, and something missing becomes empty.
  • Use dots to go deeper. A number picks an item from a list: items.0.sku is the first product in the order.
  • No loops, no conditions, no formulas. It is a fill in the blanks, nothing more.
  • The template can be up to 16 KB and 8 levels deep.
  • The request is still a POST with JSON, and it is still signed, over the body that is actually sent.

Preview before you save

The Message body section shows a live preview ("Preview, nothing is sent"). It fills your template using a real event you already received and shows the body, the destination and the header names. It sends nothing. If you have not received a real event on that endpoint yet, preview has nothing to fill in and shows an error: wait for one real event, or choose an existing row in the Event log.

Check the paths against a real event. An order event usually carries the full order, but occasionally only the order id. If a field is missing, the value is null or empty, so make sure the other service rejects an empty amount rather than accepting it.

What the response shows

For an endpoint with custom headers or a custom body, the Event log keeps the status code and the time of each delivery but not the response body, because a third-party answer can contain codes or keys. Endpoints without them keep the first 10 KB of the response.

What webhooks cannot do: no write-back, no fixed IP, one request per order

Read this before you build a voucher or licence flow on a webhook.

  • POST with JSON only. No GET, no form data, no XML.
  • One fixed URL. You cannot put the order or product in the URL.
  • One request per order, not per item. An order with 3 vouchers is one request. If the other service needs one call per voucher, you need your own server in the middle that makes those calls.
  • The body is limited to 1 MB.
  • A store can have up to 200 endpoints. Creating another is refused until you delete one you no longer use.
  • Brainerce does not read the answer and does not write anything back to the order. If the service returns a voucher code or licence key, it is not saved on the order and not sent to the customer by this feature. To put it on the order and email it, your own system has to receive the order, call the service and update the order through the API.
  • There is no fixed IP address. Brainerce sends from changing addresses. A service that only accepts calls from a list of approved IP addresses (for example Stellr-net) cannot be called from Brainerce directly. Use your own server, with an IP you have registered with them, to make the call.
  • No automatic replay. Events that were not sent or failed wait in the Event log for you to resend.

Pause or delete

  • Pause (in the row's More actions menu) stops delivery and keeps the endpoint, its settings and its secret. Activate turns it back on. Use this while you're fixing something. A paused endpoint does not record events as Not sent, so events that happen while it is Paused are not kept.
  • Delete removes it entirely, including its Event log. It is in the More actions menu and in the Delete section of the endpoint's Settings. It cannot be undone.
  • Changes in an endpoint's Settings are saved with Save changes. Closing the window with unsaved edits asks "Discard your changes?" first.

Common problems

SymptomLikely cause
The URL won't saveIt isn't https://, it points at localhost or a private network address, or it is a temporary inspection site (webhook.site, requestbin). All of these are rejected.
Nothing ever arrivesThe URL isn't reachable from the internet, or the endpoint is Paused.
Worked, then stoppedThe endpoint shows Stopped automatically. It retries itself once an hour, but a still-broken endpoint stops again on that one try. Fix the endpoint, then press Restart.
A gap in the dataLook in the Event log for rows marked Not sent and resend them. Events older than 7 days, or beyond the first 500, were not kept: re-import those from the relevant list page.
An event Failed and never came backAfter 5 tries Brainerce gives up and does not queue it again. Open the Event log and resend it.
The resend did not fix itA resent event is tried once. Check the response status in the Event log, fix the endpoint, and resend again.
The other service got the same order twiceYou resent an event that had already worked, and the other service does not check for repeats. Put the order id in the custom body (for example as ref) and have it refuse duplicates.
Send test event is refusedThe endpoint has custom headers or a custom body, so a test is a real call. Use Preview to see the body without sending, or confirm the live call.
I can't see the response from the other serviceBy design for endpoints with custom headers or a custom body: only the status code and time are kept.
A voucher or licence code did not appear on the orderBrainerce does not read the response or write it back. See "What webhooks cannot do" above.
The other service refuses every callIt may only accept approved IP addresses. Brainerce has no fixed IP: call it from your own server instead.
Signature check failsYou're verifying against a rotated (old) secret, or hashing the wrong part of the request. With a custom body, hash the body exactly as received.
Duplicate eventsNormal. Retries and resends happen with the same event id; make your handler idempotent by keying on the event id.

What's next?