ZVN

Shopify Webhooks with Google Cloud

Shopify can deliver webhooks straight to a Google Cloud Pub/Sub topic—no public endpoint on your app, no HMAC verification. In this post, we'll wire carts/create events into Pub/Sub and consume them with an Express app on Cloud Run to answer one question: which SKU gets added to carts the most?

Why Pub/Sub?

  • Shopify publishes directly—your app doesn't need to be up
  • Failed deliveries retry automatically until you ack them
  • Auth is IAM, so there's no webhook signature to verify

The Topic

Create a topic, then let Shopify's service account publish to it:

gcloud pubsub topics create shopify-webhooks
gcloud pubsub topics add-iam-policy-binding shopify-webhooks \
--member serviceAccount:delivery@shopify-pubsub-webhooks.iam.gserviceaccount.com \
--role roles/pubsub.publisher

Registering the Webhook

Declare the subscription in your app's shopify.app.toml. The pubsub:// uri points at the project and topic we just created:

[webhooks]
api_version = "2025-01"
[[webhooks.subscriptions]]
topics = ["carts/create"]
uri = "pubsub://my-project:shopify-webhooks"

Run shopify app deploy and the CLI registers the carts/create subscription for you—no Admin API call needed.

Consuming on Cloud Run

A push subscription POSTs every message to your Cloud Run URL as it arrives:

gcloud pubsub subscriptions create shopify-webhooks-push \
--topic shopify-webhooks \
--push-endpoint https://my-app-xyz-uc.a.run.app/pubsub

The Express side just unwraps the message and acks:

import express from 'express';
const app = express();
app.use(express.json());
app.post('/pubsub', (req, res) => {
// Pub/Sub wraps the webhook in a base64-encoded message
const cart = JSON.parse(
Buffer.from(req.body.message.data, 'base64').toString()
);
tallySkus(cart);
// 2xx acks the message — anything else and Pub/Sub redelivers
res.status(204).send();
});
app.listen(process.env.PORT || 8080);

Counting SKUs

Each carts/create payload carries the cart's line items. Tally quantities per SKU and the front-runner falls out:

const skuCounts = new Map<string, number>();
function tallySkus(cart: { line_items: LineItem[] }) {
for (const item of cart.line_items) {
skuCounts.set(
item.sku,
(skuCounts.get(item.sku) ?? 0) + item.quantity
);
}
const [topSku, count] = [...skuCounts.entries()].sort(
(a, b) => b[1] - a[1]
)[0];
console.log(`Most added to cart: ${topSku} (${count} times)`);
}

Conclusion

A topic, an IAM binding, one mutation, and a small Express app—Shopify events now flow through Pub/Sub with retries and scaling handled for you. Swap the topic and the handler and the same pipeline works for orders, products, or any other webhook Shopify offers.

Don't forget the Map lives in memory—Cloud Run instances come and go, so persist your counts to your DB before trusting the numbers. 😉

© Copyright Eli Zevin