How I Built a Free Package Tracking Notifier with Cloudflare Workers


I recently ordered Starlink. It shipped via DHL, and then got forwarded to a local courier that had no push notifications. I didn’t want to keep refreshing their tracking page, so I built a free notifier with Cloudflare Workers, KV, and IFTTT. This post explains the approach and gives a step-by-step guide you can follow.
How It Works (Quick Recap)
A Cloudflare Worker fetches the courier’s tracking API (JSON).
It compares the latest response with the last one stored in KV.
If there’s a change, it posts a webhook to IFTTT (which sends me a push/email).
A scheduler (cron) runs the Worker every 30 minutes.
Final Worker Script (with secrets + timeout + readable timeline)
export default {
async fetch(request, env, ctx) {
// respond immediately to avoid cron timeouts
ctx.waitUntil(handleTracking(env));
return new Response(JSON.stringify({ status: "Queued" }), {
headers: { "Content-Type": "application/json" },
});
},
};
async function handleTracking(env) {
const API_URL = env.TRACKING_API_URL; // store in wrangler.toml/env
const IFTTT_WEBHOOK_URL = env.IFTTT_WEBHOOK_URL; // stored as a secret
try {
// fetch with an explicit timeout
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 15000);
const response = await fetch(API_URL, { signal: controller.signal });
clearTimeout(timer);
if (!response.ok) throw new Error(`API error: ${response.status}`);
const current = await response.json();
// read previous snapshot
const previousRaw = await env.TRACKING_CACHE.get("previous_response");
const previous = previousRaw ? JSON.parse(previousRaw) : null;
// format timeline into readable text (limit to latest 5 entries to keep push short)
const timeline = Array.isArray(current.timeline) ? current.timeline : [];
const latest = timeline.slice(-5);
const timelineText = latest
.map(
(item, i) =>
`${i + 1}. [${item.date_time}] ${item.message} at ${item.location} (Status: ${item.status})`
)
.join("\n");
// only notify if changed (or if nothing stored yet)
const changed = !previous || JSON.stringify(previous) !== JSON.stringify(current);
if (changed) {
await fetch(IFTTT_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ update: timelineText }),
});
console.log("Change detected → IFTTT notified");
}
// persist latest snapshot
await env.TRACKING_CACHE.put("previous_response", JSON.stringify(current));
} catch (err) {
console.error("handleTracking error:", err);
}
}
Step-by-Step Setup Guide
1) Prerequisites
A Cloudflare account (Workers + KV enabled on the free tier)
Node.js + npm installed locally
An IFTTT account with the Webhooks service enabled
A courier tracking API URL (replace with yours)
2) Initialize the Worker Project
# install wrangler globally (or use npx wrangler)
npm i -g wrangler
# create a new worker
wrangler init tracking-notifier
cd tracking-notifier
3) Create a KV Namespace
Wrangler v3 syntax uses spaces, not colons.
wrangler kv namespace create TRACKING_CACHE
Copy the returned id
. If you use environments (dev/prod), also create a preview namespace:
wrangler kv namespace create TRACKING_CACHE --preview
Then add to wrangler.toml
:
name = "tracking-notifier"
main = "src/index.js"
compatibility_date = "2024-01-01"
kv_namespaces = [
{ binding = "TRACKING_CACHE", id = "YOUR_NAMESPACE_ID" }
]
# Optional: built-in Cloudflare cron (every 30 minutes)
triggers = { crons = ["*/30 * * * *"] }
[vars]
TRACKING_API_URL = "https://<>.lk:3001/api/tracking/<number>"
If you prefer environment sections:
[env.production]
kv_namespaces = [
{ binding = "TRACKING_CACHE", id = "PROD_NAMESPACE_ID" }
]
[env.preview]
kv_namespaces = [
{ binding = "TRACKING_CACHE", id = "PREVIEW_NAMESPACE_ID" }
]
4) Store Secrets (Don’t hardcode your IFTTT key)
Create the IFTTT webhook URL like:
https://maker.ifttt.com/trigger/ship/json/with/key/<REPLACE_WITH_YOUR_KEY>
Save it as a Worker secret:
wrangler secret put IFTTT_WEBHOOK_URL
# paste your full webhook URL when prompted
Now the code can read env.IFTTT_WEBHOOK_URL
.
5) Wire Up IFTTT
In IFTTT, create a new Applet.
If This → choose Webhooks, event name:
ship
.Then That → choose Notifications (or Email/Telegram/Slack).
In the message body, include
{{update}}
to display the timeline text.Go to the Webhooks Documentation page in IFTTT to confirm your key and test.
Manual test (optional):
curl -X POST "$IFTTT_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{"update":"IFTTT test from curl"}'
6) Add the Worker Code
Place the JavaScript from “Final Worker Script” into
src/index.js
.Make sure the imports/structure match your
wrangler.toml
(themain
field).
7) Test Locally & Deploy
Local dev (uses a local preview KV binding):
wrangler dev
Open the local URL shown in the terminal to trigger the fetch once.
Deploy:
wrangler deploy
Tail logs (great for debugging):
wrangler tail
8) Schedule It
Option A — Cloudflare Cron Triggers (recommended)
We already added:
triggers = { crons = ["*/30 * * * *"] }
After deploy, Cloudflare runs your Worker every 30 minutes automatically.
Option B — External Cron (cron-job.org)
Create a cron job that hits your Worker URL every 30 minutes.
Use method
GET
(orPOST
) and a timeout ≥ 10–15 seconds.Since our Worker responds immediately and does the work in the background (
ctx.waitUntil
), cron timeouts are unlikely.
9) Troubleshooting Tips
IFTTT not firing? Ensure you send
{"update":"...text..."}
in the body and your event name matches (/trigger/ship/...
).cron-job.org timeout? We respond immediately and process in the background; verify you deployed that
ctx.waitUntil
version.Large notifications truncated? Limit timeline to the last few entries (as in the sample).
KV not updating? Check your
kv_namespaces
binding name matches your code (TRACKING_CACHE
).API errors or slow courier API? Increase the timeout slightly or add retry logic.
Result
Now I get a neat push/email every time the local courier posts an update, even though they don’t offer native notifications. The stack is free, reliable, and easy to adapt for any other API you want to watch.
Happy automating! 🚀📦
Subscribe to my newsletter
Read articles from Vihanga nivarthana directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Vihanga nivarthana
Vihanga nivarthana
working on the front end to build fantastic user interfaces and deliver a top notch user experience.