Inbound Webhooks
Inklura receives webhooks from external providers on fixed routes under
https://manage.inklura.fr/api/webhooks/*. Each receiver is specific to its provider: you set
up the provider side when you connect the integration.
There is no webhook subscription API for platform events: you cannot register your own URL for Inklura to call. To send events from an app to an external system, see Outbound.
How a receiver works
- The provider sends a POST request to the receiver route.
- The receiver authenticates the request in the way its integration defines (below for Stripe, WordPress and PrestaShop).
- The event is processed, either right away or through a background queue, depending on the receiver.
provider ──POST──> /api/webhooks/<provider> ──authenticate──> processing
Payments
Stripe
| Route | Notes |
|---|---|
/api/webhooks/stripe |
Core Stripe events |
/api/webhooks/stripe-subscriptions |
Subscription lifecycle |
Verification: the Stripe-Signature header, checked with the signing secret of the
endpoint configured in Stripe (Stripe's standard verification). A missing or invalid
signature returns 400 and the event is not processed.
PayPal, crypto, and generic payments
/api/webhooks/paypal/api/webhooks/payments/api/webhooks/cryptocurrency
Commerce
WordPress
/api/webhooks/wordpress receives events from a connected WordPress site: posts, pages,
media, categories, tags, menus, products and variations (including stock changes), orders,
customers, coupons, and form leads (lead.submitted). The JSON body carries the event name
in event (for example post.updated) and the entity in id and data.
Authentication: the access key and HMAC secret of the site's WordPress connection. Each request carries these headers:
| Header | Content |
|---|---|
x-cmgr-tenant-id |
Organization id (required) |
x-cmgr-site-id |
Site id (required) |
x-cmgr-key |
The connection's access key |
x-cmgr-timestamp |
Unix timestamp of the request, in seconds |
x-cmgr-nonce |
A unique value per request |
x-cmgr-signature |
The HMAC-SHA256 signature, lowercase hex |
x-cmgr-version |
Sender version (optional) |
If you send these events from your own code, sign each request as follows:
- Concatenate, with no separator: the
x-cmgr-timestampvalue, the methodPOST, the path/api/webhooks/wordpress, and the raw request body, exactly as sent. - Compute an HMAC-SHA256 of that string (UTF-8) with the connection's HMAC secret.
- Send the result as lowercase hex in
x-cmgr-signature.
import { createHmac } from "node:crypto";
const body = JSON.stringify(event);
const timestamp = String(Math.floor(Date.now() / 1000));
const signature = createHmac("sha256", HMAC_SECRET)
.update(timestamp + "POST" + "/api/webhooks/wordpress" + body, "utf8")
.digest("hex");
await fetch("https://manage.inklura.fr/api/webhooks/wordpress", {
method: "POST",
headers: {
"content-type": "application/json",
"x-cmgr-tenant-id": TENANT_ID,
"x-cmgr-site-id": SITE_ID,
"x-cmgr-key": ACCESS_KEY,
"x-cmgr-timestamp": timestamp,
"x-cmgr-nonce": crypto.randomUUID(),
"x-cmgr-signature": signature,
},
body,
});
When the connection has an HMAC secret, every event must carry a valid signature and a timestamp within 5 minutes of the server clock (in either direction); a request replayed later is rejected.
| Case | Response |
|---|---|
| Missing organization or site header | 400 |
| Site with no WordPress connection | 404 |
| Access key does not match | 401 |
| Missing or invalid signature, timestamp outside the 5-minute window | 401 |
lead.* event on a connection without an HMAC secret |
401 |
| Event accepted | 200, with success: true, eventType and entityId |
An unknown event name is accepted (200) but ignored. A GET request on the same route
returns the supported events in supportedEvents.
PrestaShop
/api/webhooks/prestashop/<entity> receives events from the PrestaShop connector module, per
entity type: customers, products or orders. The JSON body carries an events array; each
event has an id, an action (create, update or delete), and may carry entity, data
and timestamp.
{
"events": [
{ "entity": "products", "action": "update", "id": 42, "data": { }, "timestamp": "2026-10-10T08:00:00Z" }
]
}
Authentication: the module sends your site's webhook secret (shown in the console's
PrestaShop integration settings) as is, with no HMAC computation, in the X-Webhook-Signature
header. The receiver uses it to find your site.
| Case | Response |
|---|---|
Entity other than customers, products or orders |
400 |
Invalid JSON or missing events array |
400 |
An event's entity differs from the route's |
400 |
| Missing or unknown secret | 401 |
| More than 600 requests per minute for one site | 429, with a Retry-After header |
| Batch processed | 200, with one result per event in results |
An event with no id or no action does not stop the batch: it shows up as failed in
results.
Other storefronts
/api/webhooks/woocommerce
Email providers
Bounce, open, complaint (and similar) events from email providers, one route per provider:
resend · mailgun · sendgrid · postmark · ses · sparkpost · sendinblue ·
socketlabs · mandrill · pepipost
Delivery
/api/webhooks/delivery/ubereats/api/webhooks/delivery/deliveroo
Social
Platform events (/api/webhooks/social/<network>) for:
facebook · instagram · linkedin · tiktok · twitter · youtube
Other
/api/webhooks/twilio/status: SMS and voice delivery status/api/webhooks/yousign: electronic signature events/api/webhooks/shipping/tracking: parcel tracking updates from carriers/api/webhooks/google/ucp: order status and notifications from Google Merchant Center/api/webhooks/calendar: notifications from connected calendars (Google, Outlook, Apple)
No self-serve subscription
Inklura does not let you subscribe a URL of your choice to platform events. The receivers above are the platform's provider integrations. To react to an event, do it from your app: loaders and actions, a queue worker, or a scheduled job.
Outbound
The journey WEBHOOK step
The journeys engine has a WEBHOOK step type, but today it
emits no HTTP request. Do not build anything that assumes otherwise.
Pushing from your app with a shared secret
To push data to a customer's system, your app sends a POST request to the destination itself, with a header carrying a shared secret that the destination checks.
Example: an app that publishes a daily menu to a merchant's WordPress site. The per-site secret is kept in KV.
import { createSdk } from "@bext-stack/framework/sdk";
const sdk = createSdk("<app-id>");
await fetch(`https://${host}/wp-json/my-plugin/v1/daily-menu`, {
method: "POST",
headers: {
"X-App-Secret": (await sdk.kv.get<string>(`push-secret:${host}`)) ?? "",
"content-type": "application/json",
},
body: JSON.stringify(payload),
});
On the destination side, compare the header to the expected secret before accepting the request.