Webhooks

Webhooks & HMAC

Subscribe from Apps → Develop apps → your app or with webhookSubscriptionCreate. Each topic requires its read scope.

TopicScopeWhen
orders/createread_ordersAn order is placed (online store or admin).
orders/paidread_ordersAn order becomes fully paid.
orders/editedread_ordersItems on an order are added, removed or changed in quantity.
orders/fulfilledread_ordersAll items of an order are fulfilled.
orders/cancelledread_ordersAn order is cancelled.
refunds/createread_ordersA refund is issued.
products/createread_productsA product is created.
products/updateread_productsA product is updated.
products/deleteread_productsA product is deleted (payload: { id }).

Delivery

  • POST with a JSON body — the same shape as the GraphQL Order / Product objects.
  • Headers: X-Sellify-Topic, X-Sellify-Hmac-Sha256, X-Sellify-Shop-Domain, X-Sellify-Webhook-Id (use it to deduplicate), X-Sellify-API-Version.
  • Respond with any 2xx within 10 seconds. Do slow work asynchronously. Every attempt is shown in the app's delivery log.
  • Production callback URLs must be public https:// URLs.

Verify the signature

Compute an HMAC-SHA256 of the raw request body with your app's webhook secret, base64-encode it, and compare in constant time:

JavaScript
import crypto from "node:crypto";

export async function POST(req) {
  const raw = await req.text();
  const expected = crypto.createHmac("sha256", process.env.SELLIFY_WEBHOOK_SECRET).update(raw, "utf8").digest("base64");
  const given = req.headers.get("x-sellify-hmac-sha256") ?? "";
  const ok = given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!ok) return new Response("invalid signature", { status: 401 });

  const order = JSON.parse(raw);
  // ...enqueue work, then acknowledge quickly
  return new Response("ok");
}