Webhooks (entrants)
Trackily reçoit des webhooks de plusieurs sources externes : Shopify (orders, products), WooCommerce, Stripe (paiements e-commerce + paiements natifs par compte), PayPal (par compte), et les MTA (Mailgun, SES, Postmark) pour les bounces / complaints. Chaque endpoint vérifie une signature HMAC pour rejeter les requêtes non authentifiées.
Note importante : Trackily n'émet pas de webhooks sortants génériques. Si tu cherches à propager un événement Trackily (paiement reçu, conversion enregistrée, lead capturé) vers un système externe, regarde plutôt : conversion triggers email (
/docs/email/automations), postbacks vers ad networks (/docs/api/postback), ou script tags injection (Shopify uniquement).
Liste des endpoints
| Endpoint | Source | Auth |
|---|---|---|
POST /webhook/shopify |
Shopify Admin → Notifications | HMAC-SHA256 header X-Shopify-Hmac-SHA256 |
POST /webhook/woocommerce |
WooCommerce → Settings → Advanced → Webhooks | HMAC-SHA256 header X-WC-Webhook-Signature |
POST /webhook/stripe-ecom |
Stripe (legacy — e-commerce externe) | Signature header Stripe-Signature |
POST /webhook/stripe/:account_id |
Stripe (par payment_account natif) | Signature header Stripe-Signature |
POST /webhook/paypal/:account_id |
PayPal (par payment_account natif) | PayPal webhook verification + secret |
Les webhooks MTA (Mailgun, SES, Postmark) pour bounces / complaints sont également exposés mais documentés dans leur intégration spécifique.
Pattern global — raw body capture
Pour vérifier les signatures HMAC, on a besoin du body brut (avant JSON parsing). Express body-parser détruit cette possibilité par défaut. Trackily contourne via un middleware global :
// Capture raw body for all /webhook/* routes
app.use('/webhook', express.raw({ type: '*/*', limit: '2mb' }), (req, res, next) => {
req.rawBody = req.body; // Buffer
// Best-effort JSON parse — handler choose to use parsed or raw
try { req.body = JSON.parse(req.rawBody.toString()); } catch {}
next();
});
Conséquence : dans chaque handler, req.rawBody est le Buffer original (utilisé pour le HMAC), req.body est le JSON parsé (pratique pour la business logic).
POST /webhook/shopify
Setup côté Shopify
- Shopify Admin → Settings → Notifications → Webhooks → "Create webhook"
- Event : choisir parmi
orders/create,orders/paid,orders/cancelled,orders/fulfilled, etc. - Format :
JSON - URL :
https://trackily.online/webhook/shopify - Webhook API version : v2023-10 ou plus récent
Setup côté Trackily
Settings → Integrations → Shopify webhook secret. Coller le secret HMAC fourni par Shopify (stocké chiffré).
Alternative legacy : ?key=... query param + shopify_webhook_key setting. Plus faible (leak via access logs) mais accepté pour compat.
Comportement
Le handler :
- Vérifie HMAC (
sec.shopifyVerifySignature(secret, rawBody, sigHeader)). Si KO →401 Unauthorized. - Parse le payload (order Shopify).
- Extrait le
click_idTrackily depuis l'une de 4 sources, dans l'ordre :note_attributes(recommandé —_tly_click)landing_siteURL params (_tly_click,click_id,ref)referring_siteURL paramstags(regex extract)
- Si
click_idtrouvé : matching avec laclicksrow, création d'uneconversionrow avec le revenue de l'order. - Si pas de
click_id: log + skip (l'order est enregistrée côté Shopify, juste pas attribuée).
Code de retour : 200 OK même si pas de matching (sinon Shopify retry 19 fois et tu pollues les logs).
Tracking côté checkout
Pour que _tly_click se retrouve dans note_attributes, injecte un script tag Shopify (cf. tool MCP create_script_tag) qui :
// /trackily-tag.js
(function() {
var click = new URLSearchParams(location.search).get('_tly_click')
|| document.cookie.match(/_tly_click=([^;]+)/)?.[1];
if (click && window.Shopify && window.Shopify.checkout) {
// Set as cart attribute
fetch('/cart/update.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ attributes: { _tly_click: click } })
});
}
})();
POST /webhook/woocommerce
Setup côté WooCommerce
- WP Admin → WooCommerce → Settings → Advanced → Webhooks → Add webhook
- Topic :
Order created,Order updated, etc. - Delivery URL :
https://trackily.online/webhook/woocommerce - Secret : générer un secret, copier dans Settings → Integrations → WooCommerce webhook secret côté Trackily
Comportement
Vérifie X-WC-Webhook-Signature (HMAC-SHA256 base64 du body brut avec le secret). Sinon idem Shopify : extract click_id depuis customer_note / meta_data, create conversion.
POST /webhook/stripe-ecom
Webhook Stripe global pour le e-commerce externe (legacy — quand Trackily orchestrait des paiements Stripe sans système payment_accounts par compte). Vérifie Stripe-Signature avec stripe_ecom_webhook_secret.
Évite ce path — préfère /webhook/stripe/:account_id pour le commerce natif.
POST /webhook/stripe/:account_id
Webhook Stripe par payment_account. Chaque payment_account natif a son propre webhook secret (stocké dans payment_accounts.webhook_secret, chiffré). L'URL inclut l'account_id pour identifier quel secret utiliser.
Setup
- Création du payment_account dans Trackily (Settings → Commerce → Payment methods). Récupérer son
account_id. - Stripe Dashboard → Developers → Webhooks → Add endpoint
- URL :
https://trackily.online/webhook/stripe/<account_id> - Events :
payment_intent.succeeded,payment_intent.payment_failed,charge.refunded,customer.subscription.created, etc. - Copier le
whsec_...signing secret dans Trackily UI (champ webhook_secret du payment_account).
Comportement
- Lit le payment_account par
account_id(déchiffrewebhook_secretautomatiquement viaCIPHER_TABLES). - Vérifie signature
Stripe-SignatureviaStripe.webhooks.constructEvent(rawBody, sig, webhook_secret). - Switch sur
event.type:payment_intent.succeeded→ mark order paid, déclenche post-purchase email automationpayment_intent.payment_failed→ mark order failedcharge.refunded→ record refundcustomer.subscription.created/updated/deleted→ update native_subscriptions row
- Code de retour :
200 OK(Stripe retry sinon).
Auto-création de webhook au moment du checkout
Quand un buyer paye via Stripe pour la première fois sur une instance fresh, Trackily peut auto-créer le webhook (extrait de server.js) :
const webhookUrl = `${req.protocol}://${req.get('host')}/webhook/stripe/native-pending`;
const webhookCreate = await fetch('https://api.stripe.com/v1/webhook_endpoints', { ... });
C'est best-effort — si tu préfères le contrôle explicite, désactive et configure manuellement.
POST /webhook/paypal/:account_id
Webhook PayPal par payment_account. URL : https://trackily.online/webhook/paypal/<account_id>.
Setup
PayPal Developer Dashboard → My Apps & Credentials → ton App → Add webhook :
- URL :
https://trackily.online/webhook/paypal/<account_id> - Events :
PAYMENT.CAPTURE.COMPLETED,PAYMENT.CAPTURE.REFUNDED,BILLING.SUBSCRIPTION.CREATED, etc.
Le webhook_id retourné par PayPal va dans payment_accounts.webhook_secret (overloaded : pour PayPal, ce champ stocke le webhook_id qui sert à la vérification).
Vérification
Pas une simple HMAC : PayPal demande de POST une request de vérification vers leur API avec les headers reçus + le body. Plus lourd, mais c'est leur convention. Implémenté via paypal-helpers.js.
Pattern de retry
| Source | Retry policy |
|---|---|
| Shopify | 19 retries sur 48h, exponential backoff |
| WooCommerce | configurable côté WP, défaut ~3 retries |
| Stripe | 3 jours, ~10 retries, exponential backoff |
| PayPal | 25 retries sur 3 jours |
Si ton handler crash ou retourne 5xx, attends-toi à recevoir le même payload N fois. L'idempotence est de ta responsabilité :
- Côté Trackily, on dédup via la combo
(external_event_id, source)quand disponible. - Pour les events sans ID stable (rare), on dédup par
(order_external_id, event_type)en assumant qu'un même event ne fire qu'une fois dans une fenêtre courte.
Debugging
Réception sans matching
Si un webhook arrive mais aucune conversion n'est créée :
- Vérifie côté logs
[Shopify] Webhook receivedqu'il est bien reçu. - Si pas reçu : firewall / DNS / signature secret erroné.
- Si reçu mais "No click_id found" : ton tracking côté checkout n'injecte pas
_tly_clickdans les note_attributes / landing_site. Vérifie le script tag.
Signature failure
[Shopify] HMAC verification failed
Causes habituelles :
- Secret recopié avec espaces ou retour à la ligne en trop
- Tu utilises l'API version où Shopify a changé le format de signature (rare, mais arrive)
- Un proxy en amont (Nginx, Cloudflare) modifie le body en transit (mauvaise config buffer)
Mitigations : strict timing-safe compare déjà en place, copier-coller le secret depuis Shopify directement (pas via un mot de passe manager qui ajoute des chars invisibles).
Volume
Si tu reçois >100 webhooks/seconde (gros store Shopify avec orders/* + products/* + customers/*), le rate limit de Node Express peut saturer. Mitigations :
- Désactive les events que tu n'utilises pas (côté Shopify)
- Augmente le rate limit nginx en amont
- Considère un worker queue dédié (out of scope pour Trackily 1.0)
Voir aussi
- API — index — vue d'ensemble des HTTP endpoints
- Postback — l'autre source d'event-driven (affiliate networks)
- Commerce — index — comment ces webhooks alimentent les orders natives
- Email — automations — comment
payment_intent.succeededdéclenche le post-purchase email