Event signal:order.completed| Destination:Webhook| Use case:Per-Tenant Webhooks With HMAC Signing| Typical setup:~25 minutes
Workflow outcome
The workflow starts with one order.completed event from your product and ends with a signed HTTPS POST to the URL the customer registered inside their workspace.
Each request carries an HMAC signature built from the customer's own secret. The customer's receiver recomputes the signature against the request body and accepts only matching requests.
Why teams care
Webhooks are the lowest-friction way to let customers with their own backends receive your product events. HMAC signing is what makes them trustworthy — the customer's receiver needs to verify every request is really from you.
Letting the customer provide their own URL and secret keeps your product out of per-tenant crypto. You never store, rotate, or distribute secrets — the embed and Meshes handle that surface per workspace.
The same pattern is how you set up signed webhooks for internal receivers too: a dev workspace can register a sandbox URL with its own secret while production points at the real endpoint.
This is the most portable customer-facing integration shape. If a customer is not on Slack or a supported CRM, a signed webhook is the universal escape hatch that works with whatever they run.
What it depends on
These pages stay focused on the workflow outcome, but the setup still needs the right workspace, destination connection, and event path underneath.
One workspace per customer so each tenant has its own webhook URL, secret, and delivery log.
Read moreMount the embedded Meshes pages so customers can manage their webhook URL and HMAC secret without leaving your product.
Read moreReview the webhook connection options — authentication, custom headers, payload signing, and HTTP methods — so you know what the customer can configure.
Read moreYour customer needs an HTTPS endpoint and a stored secret for verifying the signature header before trusting the request body.
The source event
For a signed webhook, the payload is whatever your product emits. What matters is that the serialized request body is what the customer verifies against. Meshes signs the exact bytes it sends, so the customer recomputes the HMAC over the same body and compares.
Event payload
order.completed{
"order_id": "ord_lannister_77",
"customer_id": "cus_lannister",
"account_id": "acc_casterly",
"total_cents": 12900,
"currency": "usd",
"items": 3,
"completed_at": "2026-04-17T18:22:00Z"
}What matters most
order_id
Stable reference the customer correlates against their own systems after verifying the signature.
customer_id + account_id
Customer-facing identifiers that stay meaningful inside the receiver's records.
total_cents + currency
Financial fields where signature verification matters most — the customer cannot afford a spoofed amount.
completed_at
Lets the customer reject requests whose timestamp is too far from the current time as a replay defense.
Field mapping view
| Event field | Destination target | Why it matters |
|---|---|---|
| order_id | Request body | Serialize the full payload as JSON so the customer can recompute the HMAC against the exact bytes. |
| customer_id + account_id | Request body | Keep the receiver's correlation fields inside the signed body so they are protected too. |
| total_cents + currency | Request body | Never move financial fields outside the signed body. |
| completed_at | Request body | Combined with the signature timestamp header, gives the receiver two layers of replay protection. |
The destination connection
The customer registers the webhook URL and HMAC secret from inside the embedded Meshes workspace UI. Your product never sees the secret — the customer enters it directly in the embed. Meshes signs every request with that secret for that workspace.
Where Meshes matters
Most teams do not need another destination. They need the destination to stay in sync without embedding its delivery quirks, retries, and mapping logic into the product code path.
Event
order.completedDestination
In the workspace, bind order.completed to the Webhook Send action and leave the payload intact. The goal is to deliver exactly what the customer signs against, so keep transformations minimal unless the rule needs them.
A sample event
This is the part teams like: the source event stays readable and product-shaped while Meshes owns the destination-facing complexity.
TypeScript example
import MeshesEventsClient from '@mesheshq/events';
const events = new MeshesEventsClient(tenant.meshesPublishableKey);
await events.emit({
event: 'order.completed',
resource: 'order',
resource_id: 'ord_lannister_77',
payload: {
order_id: 'ord_lannister_77',
customer_id: 'cus_lannister',
account_id: 'acc_casterly',
total_cents: 12900,
currency: 'usd',
items: 3,
completed_at: '2026-04-17T18:22:00Z',
},
});Destination outcome
Ask the customer to use Send Test Event inside the embedded workspace UI to fire a sample order.completed against their URL. Their receiver should accept the request after signature verification.
Operational visibility
The difference between a nice demo and a usable product workflow is whether you can see what happened when the destination is slow, misconfigured, or unavailable.
In Meshes
Why teams buy Meshes
What's next
Use Case
See the broader embedded-iPaaS pattern this guide implements across multiple customers.
Open linkDocs
Read the webhook connection reference — authentication modes, custom headers, signing, and HTTP methods.
Open linkDocs
Understand how the embedded Meshes UI mounts inside your product.
Open linkDocs
See how workspace-scoped sessions keep customer secrets from ever reaching your backend.
Open linkDocs
Mint workspace-scoped sessions from your backend so each customer only manages their own webhook and secret.
Open linkGuide
See the same embedded pattern applied to a Slack connection instead of a webhook.
Open link