Outbound webhooks
Receive every wishlist change on your own endpoint — signed, retried, and verifiable in ten lines of code.
This page shows how to receive wishlist events on your server and verify that they really came from Envie.
Subscribe
In the Shopify admin under Envie → Developers, or via the API:
curl -X POST https://api.getenvie.com/v1/webhooks \
-H "Authorization: Bearer env_sk_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/envie", "events": ["item.added", "item.removed", "list.merged", "list.shared"]}'The response contains the endpoint's secret — shown once, used for signature verification below. Store it like a password.
Event types
| Type | Fires when | data |
|---|---|---|
item.added | A product is saved | { listId, customerId, anonymousId, item, count } |
item.removed | A product is unsaved | { listId, customerId, anonymousId, item, count } |
list.merged | A guest list merges into a customer's at login | { listId, customerId, mergedCount } |
list.shared | A share link is created | { listId, shareToken, url } |
The set is frozen for v1 — new types may be added, existing ones never change shape.
Delivery
POST to your URL with a JSON envelope:
{
"id": "evt_01j9x3xka9",
"type": "item.added",
"shopDomain": "your-store.myshopify.com",
"createdAt": "2026-08-05T14:32:11.000Z",
"data": {
"listId": "lst_cmef8lmao0001",
"customerId": "6301397778619",
"anonymousId": null,
"item": {
"id": "itm_cmef8lqxz0003",
"productId": "8123456789012",
"variantId": null,
"handle": "soft-hoodie",
"titleSnapshot": "Soft Hoodie",
"addedAt": "2026-08-05T14:32:11.000Z"
},
"count": 3
}
}Headers on every delivery:
| Header | Content |
|---|---|
X-Envie-Event | The event type (item.added, …) |
X-Envie-Delivery | Unique delivery id |
X-Envie-Signature | t=<unix seconds>,v1=<hex HMAC> — see below |
Rules: 10-second timeout · any 2xx counts as delivered · non-2xx retried 5 times with exponential
backoff (1 minute → 6 hours) · 20 consecutive failures auto-disables the endpoint (with a banner in
the merchant's admin). Deliveries can arrive out of order — order by createdAt, dedupe by id.
Verify the signature
The signature is HMAC-SHA256(t + "." + rawBody, secret), hex-encoded, timestamped to block
replays. Complete Express example:
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const SECRET = process.env.ENVIE_WEBHOOK_SECRET;
const TOLERANCE_SECONDS = 300;
const app = express();
app.post("/hooks/envie", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("X-Envie-Signature") ?? "";
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!parts.t || !parts.v1 || age > TOLERANCE_SECONDS) {
return res.status(401).end();
}
const expected = createHmac("sha256", SECRET)
.update(`${parts.t}.${req.body.toString("utf8")}`)
.digest("hex");
const a = Buffer.from(parts.v1, "hex");
const b = Buffer.from(expected, "hex");
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString("utf8"));
// Handle asynchronously; respond fast.
res.status(200).end();
});The two things that matter: verify against the raw bytes (not a re-serialized parse), and compare in constant time.
Test it
curl -X POST https://api.getenvie.com/v1/webhooks/{id}/test \
-H "Authorization: Bearer env_sk_your_key"Sends a synthetic envelope with type: "ping" through the full delivery pipeline — same headers,
same signature, safe to point at production.
Common mistakes
- Verifying against parsed-then-restringified JSON. Key order and whitespace change; the
signature is over the exact bytes sent. Keep the raw body (
express.raw, or your framework's equivalent) for verification. - Doing real work before responding. You have 10 seconds; a slow handler gets retried and you
process the event twice. Queue it, return 200, work later — and dedupe by
idregardless. - Losing the secret. It's shown once at creation. Lost = delete the endpoint and create it again.
- Alerting on a single failure. Retries cover blips; alert on the auto-disable (your endpoint stops receiving anything) rather than individual 500s.