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

  1. The provider sends a POST request to the receiver route.
  2. The receiver authenticates the request in the way its integration defines (below for Stripe, WordPress and PrestaShop).
  3. 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:

  1. Concatenate, with no separator: the x-cmgr-timestamp value, the method POST, the path /api/webhooks/wordpress, and the raw request body, exactly as sent.
  2. Compute an HMAC-SHA256 of that string (UTF-8) with the connection's HMAC secret.
  3. 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.

See also