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-webhooksgcloud 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 messageconst cart = JSON.parse(Buffer.from(req.body.message.data, 'base64').toString());tallySkus(cart);// 2xx acks the message — anything else and Pub/Sub redeliversres.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. 😉