Envie
Guides

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

TypeFires whendata
item.addedA product is saved{ listId, customerId, anonymousId, item, count }
item.removedA product is unsaved{ listId, customerId, anonymousId, item, count }
list.mergedA guest list merges into a customer's at login{ listId, customerId, mergedCount }
list.sharedA 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:

HeaderContent
X-Envie-EventThe event type (item.added, …)
X-Envie-DeliveryUnique delivery id
X-Envie-Signaturet=<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 id regardless.
  • 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.

On this page