
Shopify Push Notifications: Get Alerted the Moment a Sale Happens
Set up instant push notifications for Shopify orders, refunds, and fulfillment events using Shopify webhooks and the FlowAlert API — works on any device, no app required.
Shopify Push Notifications: Get Alerted the Moment a Sale Happens
Shopify's built-in notifications are email-only, and the Shopify mobile app's push notifications are notoriously unreliable and limited. If you're serious about being the first to know when an order lands — whether you're at your desk or on the shop floor — you need a better solution.
This guide shows you how to use Shopify webhooks + FlowAlert to get instant, reliable push notifications on every order, refund, or fulfillment event.
Why Shopify Webhooks?
Shopify has a powerful webhook system that fires events for almost everything that happens in your store:
- New order placed
- Order paid / fulfilled / cancelled
- Refund created
- Product inventory updated
- Customer created
By pointing these webhooks at a small relay endpoint, you can forward the data to FlowAlert and get a push notification within seconds.
Architecture
Shopify Store
│
▼ Webhook (HTTPS POST)
Relay Endpoint (Vercel / Railway / any Node.js host)
│
▼ REST API call
FlowAlert API
│
▼ Push Notification
Your Phone
The relay is necessary because Shopify webhooks need to hit a public HTTPS URL. You can host this on Vercel for free in under five minutes.
Step 1: Create the Relay Endpoint
Create a new Next.js project (or add a Route Handler to your existing one):
1// app/api/shopify-webhook/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import crypto from 'crypto';
4
5const SHOPIFY_SECRET = process.env.SHOPIFY_WEBHOOK_SECRET!;
6const FLOWALERT_KEY = process.env.FLOWALERT_API_KEY!;
7const FLOWALERT_API = 'https://api.flowalert.io/v1/events';
8
9function verifyShopifyWebhook(body: string, hmac: string): boolean {
10 const hash = crypto
11 .createHmac('sha256', SHOPIFY_SECRET)
12 .update(body, 'utf8')
13 .digest('base64');
14 return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(hmac));
15}
16
17async function notifyFlowAlert(event: string, title: string, body: string, priority = 'NORMAL', data = {}) {
18 await fetch(FLOWALERT_API, {
19 method: 'POST',
20 headers: { 'Content-Type': 'application/json', 'X-Api-Key': FLOWALERT_KEY },
21 body: JSON.stringify({ event, title, body, priority, data }),
22 });
23}
24
25export async function POST(req: NextRequest) {
26 const rawBody = await req.text();
27 const hmac = req.headers.get('x-shopify-hmac-sha256') ?? '';
28 const topic = req.headers.get('x-shopify-topic') ?? '';
29
30 if (!verifyShopifyWebhook(rawBody, hmac)) {
31 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
32 }
33
34 const payload = JSON.parse(rawBody);
35
36 switch (topic) {
37 case 'orders/create': {
38 const total = payload.total_price;
39 const currency = payload.currency;
40 const customer = `${payload.customer?.first_name ?? ''} ${payload.customer?.last_name ?? ''}
41`.trim() || payload.email;
42
43 await notifyFlowAlert(
44 'shopify.new_order',
45 `💰 New Order #${payload.order_number} — ${currency} ${total}`,
46 `${customer} placed an order: ${payload.line_items.map((i: { title: string }) => i.title).join(', ')}`,
47 'HIGH',
48 { order_id: payload.id, order_number: payload.order_number, total, customer }
49 );
50 break;
51 }
52
53 case 'orders/paid': {
54 await notifyFlowAlert(
55 'shopify.order_paid',
56 `✅ Order #${payload.order_number} Paid`,
57 `${payload.contact_email} — ${payload.currency} ${payload.total_price}`,
58 'HIGH'
59 );
60 break;
61 }
62
63 case 'refunds/create': {
64 await notifyFlowAlert(
65 'shopify.refund_created',
66 `⚠️ Refund on Order #${payload.order_id}`,
67 `Refund of ${payload.currency} ${payload.transactions?.[0]?.amount ?? '?'} requested`,
68 'URGENT'
69 );
70 break;
71 }
72
73 case 'orders/cancelled': {
74 await notifyFlowAlert(
75 'shopify.order_cancelled',
76 `❌ Order #${payload.order_number} Cancelled`,
77 `Reason: ${payload.cancel_reason ?? 'not specified'}`,
78 'URGENT'
79 );
80 break;
81 }
82
83 case 'fulfillments/create': {
84 await notifyFlowAlert(
85 'shopify.fulfillment_created',
86 `📦 Order #${payload.order_id} Fulfilled`,
87 `Tracking: ${payload.tracking_number ?? 'N/A'} via ${payload.tracking_company ?? 'unknown'}`,
88 'NORMAL'
89 );
90 break;
91 }
92 }
93
94 return NextResponse.json({ ok: true });
95}Step 2: Deploy the Relay
vercel deployCopy your deployment URL — you'll need it for Shopify.
Step 3: Register Webhooks in Shopify
Go to Shopify Admin → Settings → Notifications → Webhooks and add:
| Event | URL |
|---|---|
| Order creation | https://your-relay.vercel.app/api/shopify-webhook |
| Order paid | Same URL |
| Refund creation | Same URL |
| Order cancellation | Same URL |
Set format to JSON for all of them.
Copy the signing secret Shopify provides and add it to your environment variables as SHOPIFY_WEBHOOK_SECRET.
Step 4: Configure FlowAlert Routing
In your FlowAlert dashboard, create routing rules — using the same event keys you set in your relay handler above:
shopify.new_order→ notify Operations, HIGH priorityshopify.refund_created→ notify Finance, URGENT, requires acknowledgementshopify.order_cancelled→ notify CS team, URGENT
These event keys are examples you define yourself. The key you send in your code and the key on your FlowAlert routing rule just need to match — you can name them whatever makes sense for your project.
Multi-Store Setup
Running multiple Shopify stores? Deploy one relay per store (or use a single relay that reads the x-shopify-domain header to identify the source) and prefix event keys accordingly: store1.new_order vs store2.new_order — then use routing rules store1.* and store2.* to route each store's events to the right team.
Summary
Shopify + FlowAlert gives you the instant push notifications the Shopify mobile app should have had from day one. Real-time, reliable, and fully customizable for who gets notified about what.


