How to set up webhooks with Shopify on Tenklo
Learn how to receive real-time Shopify events in your app using Tenklo webhooks
Shopify webhooks notify your app when something changes in a store — an order is created, a product is updated, a customer is deleted. You register webhooks against Shopify's Admin API with the callback address set to your Tenklo webhook URL; Tenklo verifies the request and routes it to the right connection.
How it works
- You register a webhook via Shopify's Admin API (REST or GraphQL), pointing the callback address at your Tenklo webhook URL.
- Shopify sends a POST request to that URL when the subscribed event occurs, signing it with
X-Shopify-Hmac-Sha256and including theX-Shopify-TopicandX-Shopify-Shop-Domainheaders. - Tenklo verifies the signature, matches the shop domain in
X-Shopify-Shop-Domainto the connection'ssubdomain, and forwards the event to your app.
Setup
1. Get your Tenklo webhook URL
In the Tenklo dashboard, open your Shopify integration and copy the Webhook URL.
2. Register the webhook in Shopify
Registering a webhook for a topic requires the scope that grants read access to the underlying resource (for example, read_orders for orders/create). Add the scope to your Shopify integration's Scopes field in the Tenklo dashboard and re-authorize the connection if it was created before the scope was added.
Shopify's REST Admin API has been legacy since October 2024, and public (OAuth) apps created after April 2025 must use the GraphQL Admin API — the REST tab below still works for existing apps and for the API Key (custom app) auth mode, but prefer GraphQL for any new OAuth integration.
curl -X POST "https://api.tenklo.dev/proxy/admin/api/2025-01/webhooks.json" \
-H "Authorization: Bearer <NANGO-API-KEY>" \
-H "Provider-Config-Key: <INTEGRATION-ID>" \
-H "Connection-Id: <CONNECTION-ID>" \
-H "Content-Type: application/json" \
-d '{
"webhook": {
"topic": "orders/create",
"address": "<NANGO-WEBHOOK-URL>",
"format": "json"
}
}'Repeat for each topic you want to receive. REST topics use lowercase slash-separated names (orders/create); the GraphQL enum uses the equivalent screaming-snake-case name (ORDERS_CREATE).
Webhook payload
The POST body is the raw resource as JSON — the same shape as the REST Admin API response for that resource, with a numeric id (not a GraphQL global ID), regardless of whether you registered the webhook via REST or GraphQL. Example for orders/create:
{
"id": 820982911946154508,
"order_number": 1001,
"email": "[email protected]",
"financial_status": "paid",
"currency": "USD",
"total_price": "398.00",
"line_items": [
{
"id": 866550311766439020,
"title": "IPod Nano - 8GB",
"quantity": 1,
"price": "199.00"
}
],
"customer": {
"id": 115310627314723954,
"email": "[email protected]"
}
}Every delivery also includes these headers:
| Header | Description |
|---|---|
X-Shopify-Topic | The subscribed topic, e.g. orders/create. |
X-Shopify-Shop-Domain | The store's myshopify.com domain — used for connection matching. |
X-Shopify-Hmac-Sha256 | Base64-encoded HMAC-SHA256 signature — see Signature verification. |
X-Shopify-Webhook-Id | Unique per delivery. Use it to deduplicate retries — Shopify redelivers on a non-2xx response or timeout, so expect occasional duplicates. |
X-Shopify-Event-Id | Shared across every subscription's delivery for the same merchant action. If you have multiple webhooks on the same topic, use this (not X-Shopify-Webhook-Id) to correlate them. |
X-Shopify-Triggered-At | When the event occurred — use it to order out-of-sequence deliveries. |
X-Shopify-API-Version | The API version the subscription was created with. |
Connection matching
Tenklo matches an incoming webhook to a connection using the shop domain in the X-Shopify-Shop-Domain header against the connection's subdomain. No extra configuration is needed on the connection.
If no connection's subdomain matches the shop domain in X-Shopify-Shop-Domain, the webhook is not routed to any connection.
Signature verification
- Shopify (OAuth): Nango verifies
X-Shopify-Hmac-Sha256automatically using your integration's client secret — no extra setup needed. - Shopify (API Key): set a Webhook Secret on the integration in the Tenklo dashboard, matching the secret configured when you subscribed the webhook, so Tenklo can verify the signature against it.
Handle the webhook
Once routed to a connection, you have two options:
- Forward it to your app — Tenklo forwards the event to your webhook URL with connection attribution. See External webhook forwarding.
- Process it in a sync — run a sync when the webhook arrives using
webhookSubscriptionsandonWebhookin a sync script. See Real-time syncs.
Supported events
Tenklo can route any topic Shopify delivers, since routing only depends on the X-Shopify-Shop-Domain header. Common topics:
| Topic | Description | Required scope |
|---|---|---|
orders/create | An order was created | read_orders |
orders/updated | An order was updated | read_orders |
orders/cancelled | An order was cancelled | read_orders |
products/create | A product was created | read_products |
products/update | A product was updated | read_products |
customers/create | A customer was created | read_customers |
customers/update | A customer was updated | read_customers |
app/uninstalled | A merchant uninstalled your app | none |
For the full list, see Shopify's webhook topics reference.
Rollback strategy
To stop webhooks, delete the subscription using the id Shopify returned when you created it:
- REST: send a
DELETErequest to/admin/api/2025-01/webhooks/<id>.json. - GraphQL: call the
webhookSubscriptionDeletemutation with thatid.
Deleting a subscription stops delivery immediately. Re-create it with the steps above to resume.