Order webhooks (store to Idukki)
Attribution is reconciled, not modelled: a completed order is matched back to the add-to-cart the widget recorded. That needs the store to tell Idukki about orders. Where that stands per platform:
| Platform | Status | How it works |
|---|---|---|
| Shopify | Live | The Shopify app registers orders/create (and orders/paid) on install. Deliveries are HMAC-verified over the raw body with X-Shopify-Hmac-Sha256; cart attributes _idk_vid, _idk_biz, _idk_exp, _idk_var come back as note_attributes and are matched to the widget’s impression, click and add-to-cart rows. Deduplicated by order id, and a webhook row replaces any client-side purchase for the same visitor so revenue is never counted twice. |
| WooCommerce | Live | Pair the store (Settings, Integrations). Then add an order.created / order.updated webhook to /webhooks/woocommerce/orders. Deliveries are HMAC-verified with X-WC-Webhook-Signature and land in the same orders list as Shopify. Each order is matched back to widget activity on arrival. Matching uses the customer id and a salted ip and user-agent fingerprint (always marked estimated). It also uses _idk_vid when the store saves the visitor id on the order. The Idukki for WooCommerce plugin (early access) does that at checkout. |
| BigCommerce, Magento | Live | Both have order webhooks: /webhooks/bigcommerce/orders and /webhooks/magento/orders/{token}. You connect them from Settings, Integrations, and orders are recorded alongside Shopify’s. Each order is matched on arrival using the customer id and cart. BigCommerce sends cart_id; Magento sends quote_id, plus _idk_vid if a store module adds it. Neither order carries a user agent, so there is no fingerprint match. |
| Magento, PrestaShop, SFCC modules | Early access | The Idukki store modules send each order to POST /webhooks/partner/orders, authenticated with a store connection key (Authorization: Bearer idk_pc_…), carrying the widget visitor id, customer id and cart id but no shopper name, email or address. Idempotent on the order id. See the install guides. |
| Custom / headless | Manual | Send window.idukki.track(‘purchase’, revenue) from your confirmation page for A/B ground truth, or talk to us about a custom order endpoint. |
Integration alert webhooks (Idukki to you)
The older alert webhooks live under Settings, Integrations, on the Webhooks tab. Register any HTTPS URL there. Make.com, n8n, Zapier or your own endpoint all work. Pick the events, set an optional signing secret. Every delivery is a POST with the same envelope. For new work, use the signed webhooks below.
POST https://your-endpoint.example/idukki
content-type: application/json
x-idukki-signature: sha256=<hex HMAC-SHA256 of the exact body, when a secret is set>
{
"event": "rights.granted",
"businessId": "YOUR_BGUID",
"payload": { … },
"timestamp": "2026-09-07T10:15:02.412Z"
}import { createHmac, timingSafeEqual } from 'node:crypto'
export function verifyIdukki(rawBody, header, secret) {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex')
return header && header.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected))
}Delivery rules
- Fire-and-forget from the product’s hot paths: a slow or failing endpoint never blocks a lead capture, a rights decision or an order.
- Eight-second timeout per delivery. Return a 2xx quickly and do the work asynchronously.
- A 404 or 410 from your endpoint prunes the subscription (the Make.com convention for a deleted scenario). Re-add it in the dashboard.
- The last delivery time and status are shown per webhook in the dashboard.
Event catalogue
Two families. Product events come from the central dispatcher and are what Slack, Zapier, Teams, the helpdesk integrations and outbound webhooks all receive. Widget events are the ones the dashboard’s webhook screen lists for widget and experiment lifecycle.
| Event | Fires when |
|---|---|
ugc.collected | A new post lands in a collection from a connected source, upload or competition entry. |
review.created | A review arrives from a connected review source. |
rights.granted | A creator consents (form, DM reply or hashtag entry under published terms). |
rights.denied | A creator declines, or a reviewer rejects. |
rights.expiring | A licence is inside its renewal window. |
rights.expired | The licence window closed; the post has left every widget. |
collection.archived | A collection was archived. |
lead.captured | A lead form on a widget was submitted. |
survey.response | A survey answer was recorded. |
experiment.winner | A winner was declared on an A/B test. |
purchase.attributed | An order webhook was reconciled to a widget interaction (Shopify today). |
content.flagged | Moderation or a visitor report flagged a post. |
| Widget event | Fires when |
|---|---|
widget.impression | A widget rendered for a visitor. |
widget.click | A tile, hotspot or product link was clicked. |
widget.conversion | An add-to-cart or purchase was attributed to the widget. |
widget.config_updated | A widget’s settings were saved. |
widget.version_restored | A previous widget version was restored. |
experiment.started | An A/B test started on a widget. |
experiment.completed | An A/B test ended. |
experiment.variant_winner | A variant was promoted. |
Signed webhooks: Settings, Webhooks
In the dashboard, open Settings, then Webhooks. Add an https endpoint and pick from five events. You can send a test, rotate the secret and read the delivery log there. Each delivery is signed with X-Idukki-Signature over a timestamp and the raw body. Failed deliveries are retried. Up to 10 webhooks per account.
To manage them from code, use GraphQL: createOutboundWebhook, updateOutboundWebhook, deleteOutboundWebhook, rotateOutboundWebhookSecret, testOutboundWebhook, outboundWebhooks and outboundWebhookDeliveries. The setup guide at /help/webhooks-guide walks through both.
| Event | Sent when |
|---|---|
ugc.created | A shopper upload reaches the review queue, or a post is imported. Both arrive pending. |
ugc.approved | A post is approved in the dashboard, or a shopper upload is approved in review. |
rights.granted | A creator approves on the consent form, or rights are approved in bulk. |
rights.revoked | Rights are revoked in bulk. |
order.attributed | A live order from a store order webhook is tied to UGC as proven or in an influence tier. Backfills do not send it. |
POST https://your-endpoint.example/idukki
Content-Type: application/json
X-Idukki-Event: ugc.created
X-Idukki-Delivery: 1234
X-Idukki-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + raw body)>
{ "id": "evt_1234", "type": "ugc.created", "createdAt": "2026-10-04T10:15:02.412Z", "data": { … } }import { createHmac, timingSafeEqual } from 'node:crypto'
export function verifyIdukkiWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
if (!signatureHeader) return false
const parts = Object.fromEntries(
signatureHeader.split(',').map((p) => p.trim().split('=')).filter((p) => p.length === 2),
)
const t = Number(parts.t)
if (!Number.isInteger(t) || !parts.v1) return false
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSeconds) return false
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
const a = Buffer.from(expected, 'utf8')
const b = Buffer.from(parts.v1, 'utf8')
return a.length === b.length && timingSafeEqual(a, b)
}- Any 2xx is delivered; 10-second timeout; redirects are not followed.
- Failures retry with the same body at the earliest 1 min, 5 min, 30 min, 2 h and 6 h later: six attempts, then
failed.X-Idukki-Deliveryand the envelopeidstay the same across retries, so de-duplicate on them. - https only. Private, loopback, link-local, CGNAT and reserved addresses,
localhostand internal host names are refused, and every connection re-checks the resolved address. - The
whsec_secret is returned once, on create and on rotate. Payloads never carry a shopper email or phone. - Not the same format as the dashboard webhooks above, which sign
sha256=<hex>over the body alone and use the event names in the catalogue above. Tell them apart by the signature header value.
Webhooks Idukki receives from platforms
For completeness, the inbound routes on the API that platforms call. You do not configure these; the Shopify app and the Stripe integration register them.
| Method | Path | Auth | Summary |
|---|---|---|---|
| GET | /payment/checkout | session | Payment Api |
| GET | /payment/invoices | session | Payment Api |
| GET | /payment/portal | session | Payment Api |
| POST | /payment/webhook | public | Payment Api |
| POST | /resend/webhook | public | Misc Routes Api |
| GET | /shopify/detail/theme-page | session | Shopify Api |
| GET | /shopify/email-config | session | Shopify Api |
| POST | /shopify/order-detail | public | Shopify Api |
| POST | /shopify/order-stats | session | Shopify Api |
| GET | /shopify/order/detail/{id} | public | Shopify Api |
| POST | /shopify/save/email-config | session | Shopify Api |
| POST | /shopify/webhook/getcustomerdata | public | Shopify Api |
| POST | /shopify/webhook/order | public | Shopify Api |
| POST | /shopify/webhook/removecustomerdata | public | Shopify Api |
| POST | /shopify/webhook/shopdata | public | Shopify Api |
| POST | /shopify/webhook/uninstall | public | Shopify Api |
Only the webhook routes matter here; the rest of the Shopify group is on the API reference.