Azure Messaging

Azure Notification Hubs Explained: One Backend, Push to iOS, Android, and Web

You shipped an app on iOS and Android plus a progressive web app, and now product wants a “flash sale starts in 10 minutes” banner on every device at once. Simple, right? Except Apple devices only accept pushes from APNs (Apple Push Notification service), Android wants FCM (Firebase Cloud Messaging), the browser speaks Web Push over VAPID — each a different protocol, credential, payload shape, and way of telling you a token went stale. Build that yourself and you are running three delivery clients, storing millions of tokens, batching sends to dodge throttling, and pruning dead tokens forever — plumbing that has nothing to do with your product and never stops needing maintenance.

Azure Notification Hubs is the managed service that collapses all of that into one backend call. You send a single message to the hub; it holds every device’s registration, knows each one’s platform, translates your message into the right native payload, and pushes it through APNs, FCM, Web Push, and the Windows and Amazon stacks on your behalf — to one device, a segment, or millions, in a single operation. It is a broadcast and fan-out engine for push, not a general message queue and not a real-time socket. Knowing exactly what it is — and the four or five services it is constantly confused with — is the point of this article.

By the end you will hold a crisp mental model of how a push travels from your backend through the hub to a lock screen, what a registration and a tag actually are, when Notification Hubs is the right tool versus SignalR, Event Grid, or Service Bus, and how to stand up a hub, register a device, and send a targeted notification with az CLI and Bicep. This is a concept-first explainer: the model first, then comparison grids and decision tables, then a hands-on lab and a short troubleshooting playbook for the failures everyone hits the first week.

What problem this solves

The pain is platform fan-out. A push notification is not one thing — it is N things, one per platform, each a walled garden with its own rules. Apple wants a token (.p8) or certificate connection to APNs; Google moved Android off the legacy server key to FCM v1 (service-account JSON + OAuth2, a migration that broke many homegrown senders); the browser needs a VAPID key pair and an encrypted Web Push payload. Write to these directly and you maintain four codebases that break independently whenever a platform changes its API.

What breaks without a hub: you store and index every token yourself (tens of millions of churning rows), implement per-platform batching/retry so a broadcast does not throttle APNs, and build the feedback loop that removes tokens APNs/FCM report as unregistered — because pushing to dead tokens degrades your sender reputation. Teams routinely under-build that loop, then wonder why delivery rates quietly fall over months.

Who hits this: anyone with a mobile app (native or cross-platform like .NET MAUI, React Native, Flutter) plus often a web client, who needs engagement, transactional, or operational push — “your order shipped”, “you have a new message”, or a broadcast to a segment. The moment you push to more than one platform, or to a segment rather than a single token, the do-it-yourself cost crosses the line. The core problem the hub removes:

The push problem Doing it yourself With Notification Hubs
Talk to each platform Maintain APNs + FCM + Web Push + WNS clients One send API; hub translates per platform
Store device identities Build a token store for tens of millions of rows Hub holds registrations/installations
Target a segment Join your own user → token tables, batch sends Tags + tag expressions, one call
Broadcast at scale Hand-roll batching/throttle/retry Hub fans out to millions per operation
Prune dead tokens Build a feedback processor per platform Hub auto-expires on PNS feedback
Per-platform payload shape Format aps, android, Web Push by hand Templates render one message per platform

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should be comfortable with a mobile app that runs on a device and a backend (an API, an Azure Function, a web app) it talks to, know roughly what a REST call and a JSON payload are, and have the Azure CLI available (Cloud Shell is fine). No mobile-dev expertise is required; the lab uses CLI and a test send so you see a hub work without shipping an app.

This sits in the messaging and engagement layer, next to but distinct from the eventing and queueing services. Your backend is typically an Azure Functions and Serverless Patterns: Event-Driven Compute app or App Service that decides when to notify and calls the hub; APNs/FCM credentials belong in a vault (Azure Key Vault: Secrets, Keys and Certificates Done Right); and Message Queues vs Pub/Sub: Choosing an Async Pattern frames where push fits among queues and pub/sub. In short: the mobile team owns the app SDK, the PNS is the hub’s downstream, your backend triggers sends, Key Vault holds the credentials, and Azure Monitor reads send telemetry.

Core concepts

Five mental models make everything else obvious.

The hub is a translator and a megaphone, not a mailbox. Your backend hands it one logical message — “Flash sale!” — and it does two jobs: translates it into each platform’s native payload (an aps dictionary for Apple, an FCM message for Android, an encrypted Web Push body for the browser), and fans out, pushing to one device or to millions in a single operation. It does not store messages for a device to pull later; if a device is offline, delivery is the PNS’s problem. Think broadcast amplifier, not queue.

A device registers first with its platform, then with the hub. Push always originates from the platform owner: an iOS app gets a device token from APNs, an Android app a registration token from FCM, a browser a Web Push endpoint. That platform-issued token is the device handle. The app sends it to the hub so it knows “this iOS device exists, here is how to reach it.” Without the handle the hub cannot push — the handle is the address.

A registration (or installation) is the hub’s record of one device. Per device, the hub stores the handle (PNS + token), any tags, and optionally a template. Two shapes exist: the older registration model and the newer, recommended installation model (richer JSON, per-device template/tag updates, idempotent by a stable installationId). Both mean the same thing to the fan-out engine: a row saying “push here, it has these labels.”

Tags target a segment without your own database. A tag is a string label on a registration — country_IN, user_8842, topic_sports, pro_tier. At send time you push to a tag or a tag expression ((country_IN && pro_tier)), and the hub delivers only to matching devices. This turns “broadcast to everyone” into “broadcast to paying users in India” with no join of user to token tables. One device can carry many tags, landing one user in many segments at once.

Templates let one send render differently per platform — and per language. Normally your backend formats the payload itself. Templates flip that: each device registers a skeleton with $(placeholders), and your backend sends only the values. The hub renders each device’s own template, so one send reaches iOS, Android, and web in each platform’s correct shape, and you can localize by registering a per-language template per device — a little registration-time complexity for a much simpler, platform-agnostic send.

Those five terms in bold — handle, registration/installation, tag, template, plus the PNS they all hang off — are the entire vocabulary; the Glossary at the end repeats each for quick lookup, and the credential and SAS-policy concepts arrive in their own sections below.

How a push actually travels (the path)

Follow one notification end to end. There are five hops, and almost every problem you will ever debug lives at exactly one of them.

Hop 1 — the app gets a handle from its PNS. On launch, after the user grants permission, the app’s SDK receives a token (APNs device token, FCM registration token, or browser push subscription). Tokens rotate on reinstall, OS update, or periodically, so treat them as volatile.

Hop 2 — the app registers the handle with the hub, plus any tags and a template. With the installation model this is one idempotent upsert keyed by a stable installationId, so re-registering each launch refreshes the record rather than duplicating it.

Hop 3 — your backend decides to push and calls the hub. A business event fires (“order shipped”); the backend calls the send API with a message and a target (broadcast, tag, or tag expression), using a Full-access SAS policy. It never talks to APNs/FCM directly.

Hop 4 — the hub translates and fans out. It matches registrations, renders the right payload per platform (templated or native), and pushes batches to APNs, FCM, and Web Push, managing throughput and retries against each PNS.

Hop 5 — the PNS delivers and reports back. The PNS does last-mile delivery to the lock screen and returns feedback: a token reported Unregistered/NotRegistered (app uninstalled) makes the hub automatically expire that registration — the dead-token pruning you would otherwise build by hand.

The same path as a table — what each hop owns and where it fails:

Hop What happens Owned by Most common failure here
1. Get handle App gets APNs/FCM/Web Push token App + PNS Permission denied → no token at all
2. Register App upserts handle+tags to hub App → hub Stale/duplicate handles; missing tags
3. Backend send Backend calls hub send API Backend → hub Wrong/Listen-only SAS; bad target
4. Translate + fan-out Hub renders payload, pushes per PNS Hub → PNS Bad/expired PNS credential
5. Deliver + feedback PNS delivers; reports dead tokens PNS → hub Throttling; silent drop; token expired

Registration vs installation — pick installation

You can model a device with the legacy registration API or the newer installation API. They both produce a record the fan-out engine reads, but the installation model is what you should build on today: it is JSON, keyed by a stable installationId, and lets you patch tags and templates per device idempotently. Side by side:

Aspect Registration (legacy) Installation (recommended)
Identity Server-assigned registration ID Your stable installationId (e.g. a GUID)
Idempotency Re-register can duplicate Upsert by installationId — no dupes
Shape XML-ish per platform One JSON document
Per-device templates Supported, clunkier First-class templates map
Partial updates Replace whole registration JSON-patch individual tags/templates
Tag editing Per registration Per installation, patchable
When to use Maintaining older code All new apps

Targeting: broadcast, tags, and single device

Targeting is undervalued until you need it, and tag expressions are what make the hub a real engagement platform rather than a dumb broadcaster. Broadcast hits every registration — fine for “the service is back up”, dangerous for marketing (you annoy everyone and tank opt-in rates). Tag-based targeting reaches devices carrying a tag (topic_cricket, country_IN). Tag expressions combine tags with boolean logic, so (country_IN && pro_tier && topic_cricket) reaches only paying cricket fans in India. Single-device is the degenerate case: attach a unique user_8842 tag (or use the installation’s userId) and target it.

The mechanics and limits of each approach:

Targeting mode How you target Typical use Key limit / gotcha
Broadcast Send with no tag “Service restored”, app-wide news Hits everyone; reserve for rare global pushes
Single tag topic_sports Topic/interest segments Tag must be set at registration
Tag expression (a && b) || c Multi-attribute segments Expression length is bounded; keep it simple
Single device Unique per-user tag Transactional (“order shipped”) A device may have many handles (phone + tablet)

A few rules that save grief. Tags are set by the client at registration (or patched later) — the hub knows a user’s attributes only if a tag encodes them, so design your taxonomy up front around five patterns: high-cardinality identity (user_8842, for single-device sends), low-cardinality geography (country_IN), language (lang_en/lang_hi, which pairs with templates), interest/topic (topic_cricket, opt-in), and tier/segment (tier_pro, beta). Because there are per-registration tag limits and tag-expression complexity limits, do not model every attribute as a tag — push identity plus a handful of segment tags and keep volatile state in your backend.

Notification Hubs vs the services it gets confused with

This section resolves 80% of the confusion. “I need to notify users” maps to different Azure services depending on what “notify” means — pick wrong and you try to broadcast push from a socket service (impossible at scale) or drive a live dashboard from a hub (it cannot talk back to a connected client). The decisive question is who the recipient is and whether the app is in the foreground.

Service What it delivers Recipient Connection model Use it when
Notification Hubs OS-level push to a device Devices via APNs/FCM/Web Push Fire-and-forget through a PNS App is backgrounded/closed; lock-screen alerts; mass broadcast
Azure SignalR Service Real-time messages to a connected client Open web/app sockets Persistent WebSocket App is open: live chat, dashboards, presence
Event Grid Discrete events to subscribers Services / webhooks / functions Push (HTTP) pub-sub React to “blob created” — system events, not user alerts
Service Bus Durable messages/commands Backend consumers Pull queues / topics Reliable backend work, ordering, transactions
Event Hubs High-throughput event stream Stream consumers Partitioned log Telemetry/ingestion at millions/sec

The one-line rule: closed app → Notification Hubs; open tab to live-update → SignalR; “blob uploaded” event → Event Grid; reliable ordered processing → Service Bus; telemetry ingestion → Event Hubs. The hub alone targets devices through a PNS; the rest target services or connected sockets.

A common, correct combination: Event Grid or Service Bus triggers a Function, which calls Notification Hubs — the eventing service handles the system side (“order shipped” landed), the hub the device side. They are partners, not competitors; SignalR and the hub also coexist, SignalR updating the app while it is open and the hub catching the user when it is closed.

Credentials: how the hub authenticates to each platform

The hub can only push to a PNS if you give it that platform’s credential — the most common source of “nothing is being delivered” (a wrong, expired, or wrong-environment credential). Each platform has its own:

Platform (PNS) Credential the hub needs Modern form Notes / gotchas
Apple (APNs) Token (.p8 key) or certificate (.p12) Token-based (.p8) — recommended Token keys don’t expire yearly like certs; pick Sandbox vs Production correctly
Google (FCM) Service-account JSON FCM v1 Legacy server key is retired — must use v1 + service account
Web Push VAPID key pair VAPID (public/private) Browser subscriptions; public key shipped to the client
Windows (WNS) Package SID + secret (Entra-based) WNS (Entra app) For UWP/Windows apps only
Amazon (ADM) Client ID + secret ADM For Fire/Amazon devices

Two distinctions cause most credential incidents. First, APNs has separate Sandbox and Production environments — a dev-signed build talks to Sandbox, a TestFlight/App Store build to Production, and a token registered against one will not deliver from the other (you get a clean 200 from the hub and silence on the device). Second, FCM v1 replaced the legacy API — an old server key now fails; you must upload the service-account JSON and use the FCM v1 platform. Keep both in Azure Key Vault: Secrets, Keys and Certificates Done Right, never in source or plaintext app settings.

Architecture at a glance

Walk the diagram left to right. On the far left, three client devices — iOS, Android, and a browser — each obtain a device handle from their respective PNS and register it (with tags like country_IN, topic_cricket) into the Notification Hub. The hub, inside a namespace, is the center: it holds every device’s registration/installation, the tags that segment them, and the APNs .p8, FCM v1, and VAPID credentials it needs downstream. To its left, your backend — a Function or App Service triggered by a business event — issues one send with a target (broadcast, tag, or tag expression) using a Full SAS policy.

When a send arrives, the hub matches registrations, renders the right native payload per platform, and fans out to the three PNS endpoints on the right; each PNS does last-mile delivery and reports dead tokens back so the hub auto-expires them. The numbered badges mark what fails in practice: the SAS policy on the send call, the per-platform credential, the APNs Sandbox-vs-Production split, and the FCM v1 migration. Follow the arrows and you can place any “my push didn’t arrive” symptom on exactly one hop.

Left-to-right architecture of Azure Notification Hubs: iOS, Android and web clients register device handles via APNs, FCM and Web Push into a notification hub inside a namespace; an Azure Functions backend sends one targeted message using a Full SAS policy; the hub holds registrations, tags and the APNs .p8, FCM v1 and VAPID credentials, then translates and fans out to each PNS for last-mile delivery, with numbered badges on the SAS policy, the PNS credentials, the APNs Sandbox/Production split and the FCM v1 migration.

Real-world scenario

Kreeda, a fictional Bengaluru sports-streaming startup, runs apps on iOS, Android, and a PWA with about 1.4 million monthly active devices. They wanted three kinds of push: transactional (“subscription renews tomorrow”), topical (“⚡ India needs 12 off the last over — watch live”), and the occasional global (“maintenance tonight 2–3 AM IST”). Their first version pushed straight to APNs and FCM from a Node service, and it unravelled at scale: the FCM legacy key broke after Google’s v1 migration, their token table grew to 40+ million unpruned rows so delivery sagged, and a “all cricket fans” broadcast meant a hand-rolled loop that APNs throttled — half the audience got the alert ten minutes late, after the over was bowled.

They moved to a single Standard Notification Hub in Central India. Each app registered an installation keyed by a per-device GUID, attaching tags: user_<id>, country_IN, lang_en/lang_hi, and per-match topic_<matchId> when a user opened a match. Their existing event pipeline already produced “match event” messages on Service Bus; an Azure Function consumed those and called the hub with a tag expression like (topic_match_8841 && lang_en) for English subscribers and a parallel send for lang_hi, using templates so each platform and language rendered correctly from one set of values.

The results were the kind a hub is built to deliver. The match-day broadcast that used to leak over ten minutes now fanned out in seconds, because the hub batched against each PNS for them. Dead-token pruning became automatic — uninstalls reported by APNs/FCM expired the registrations, and delivery rates climbed back as the dead ~18% of tokens aged out. The FCM v1 migration that had broken their homegrown sender was a one-time credential upload. And because targeting moved into tags, marketing could request “paying Hindi-speaking cricket fans” — (tier_pro && lang_hi && topic_cricket) — with no database join. The one scar: an early week of “iOS gets nothing”, caused entirely by an APNs Sandbox credential on a hub serving an App Store (Production) build — a five-minute fix once they knew the split exists, which is why it leads the troubleshooting table below.

Advantages and disadvantages

The trade-off is managed cross-platform fan-out versus a purpose-built broadcaster that deliberately does not do queueing, real-time, or per-message delivery guarantees.

Advantages Disadvantages
One backend call → all platforms (APNs/FCM/Web/WNS/ADM) Push is best-effort — no per-device delivery guarantee (PNS behaviour)
Scales to millions per send with platform batching/throttling Not a queue: no ordering, no replay, no dead-letter for app logic
Tag-based segmentation without your own token DB Tag taxonomy must be designed up front; limits on tags/expressions
Templates render the right payload per platform/language Cannot message an open connected client — that’s SignalR’s job
Auto-pruning of dead tokens via PNS feedback You still own credential lifecycle (APNs env, FCM v1 JSON)
Generous Free tier; pay mainly per push at scale Rich open/click analytics need extra wiring (Insights/your own)

When each side matters: the advantages dominate any consumer app that must reach users on the lock screen across platforms. The disadvantages matter only when you mistake the hub for something it is not — ordered processing is Service Bus, live dashboard updates are SignalR, open/conversion analytics need separate instrumentation. Used for its actual job, the disadvantages are non-issues; used as a general message bus, they are blockers.

Hands-on lab

Stand up a namespace and hub, inspect access policies, and send a notification — all from the CLI, free-tier friendly. You do not need a real mobile app: the lab uses the debug/test send so you can watch a hub work end to end. Replace names as needed.

Step 1 — variables and resource group.

RG=rg-nh-lab
LOC=centralindia
NS=nhns-kreeda-$RANDOM      # namespace name must be globally unique
HUB=nh-kreeda

az group create --name $RG --location $LOC -o table

Step 2 — create the Notification Hubs namespace and a Free hub. The namespace is the billable container; the hub lives inside it.

# Create the namespace (sku Free for the lab)
az notification-hub namespace create \
  --resource-group $RG --name $NS --location $LOC --sku Free -o table

# Create the hub inside the namespace
az notification-hub create \
  --resource-group $RG --namespace-name $NS --name $HUB --location $LOC -o table

Expected: both commands return the created resource with provisioningState: Succeeded.

Step 3 — inspect the access policies (SAS). A new hub ships with two policies: a Listen policy for clients (register only) and a Full policy for your backend (send + manage). This separation is the security model.

az notification-hub authorization-rule list \
  --resource-group $RG --namespace-name $NS --notification-hub-name $HUB \
  --query "[].{name:name, rights:rights}" -o table

Expected: a DefaultListenSharedAccessSignature (Listen) and a DefaultFullSharedAccessSignature (Manage, Send, Listen).

Step 4 — grab the Full connection string for backend sends. Your backend uses this; never ship it to a client.

az notification-hub authorization-rule list-keys \
  --resource-group $RG --namespace-name $NS --notification-hub-name $HUB \
  --name DefaultFullSharedAccessSignature \
  --query primaryConnectionString -o tsv

Step 5 — (real apps) upload the PNS credentials. Skip for the lab; in production you upload APNs and FCM v1 here so the hub can authenticate downstream. The APNs (token-based) call:

# Apple APNs, token-based (.p8) — Sandbox for dev builds, Production for store builds
az notification-hub credential apns update \
  --resource-group $RG --namespace-name $NS --notification-hub-name $HUB \
  --apns-certificate "" \
  --key-id <APNS_KEY_ID> --app-id <TEAM_ID> --app-name <BUNDLE_ID> \
  --token "$(cat AuthKey_XXXX.p8)" --endpoint "https://api.push.apple.com:443/3/device"

FCM v1 is configured with the service-account JSON via the portal or the management API (the legacy server-key path is retired). Treat both credentials as secrets and store them in Key Vault.

Step 6 — send a test notification. The test send delivers to a small number of registered devices and returns the outcome, validating wiring before your app is live. A native APNs-shaped payload:

az notification-hub test-send \
  --resource-group $RG --namespace-name $NS --notification-hub-name $HUB \
  --notification-format apple \
  --message '{"aps":{"alert":"Hello from Notification Hubs"}}'

Expected: a JSON result with success / failure counts and per-registration outcomes. With no registered Apple devices yet, you will see zero successes — that is correct; it proves the send path and credential plumbing work.

Step 7 — the same hub as Bicep (for real deployments). Infrastructure-as-code version of steps 2–3:

param location string = resourceGroup().location

resource ns 'Microsoft.NotificationHubs/namespaces@2023-09-01' = {
  name: 'nhns-kreeda-prod'
  location: location
  sku: { name: 'Standard' }   // Free | Basic | Standard
}

resource hub 'Microsoft.NotificationHubs/namespaces/notificationHubs@2023-09-01' = {
  parent: ns
  name: 'nh-kreeda'
  location: location
}

// A Listen-only policy for clients to register with
resource listenRule 'Microsoft.NotificationHubs/namespaces/notificationHubs/authorizationRules@2023-09-01' = {
  parent: hub
  name: 'client-listen'
  properties: { rights: [ 'Listen' ] }
}

Step 8 — teardown. Delete the resource group to remove the namespace, hub, and policies.

az group delete --name $RG --yes --no-wait

Common mistakes & troubleshooting

The hub returns a clean success while the device stays silent more often than any other Azure service, because the delivery failure happens at the PNS, one hop past the hub. Here is the playbook — symptom, root cause, how to confirm, fix. The first two rows are the near-universal first-week failures.

# Symptom Likely root cause How to confirm Fix
1 iOS gets nothing; hub reports success APNs Sandbox vs Production mismatch (credential env ≠ build env) Hub’s APNs endpoint vs how the app was signed (dev → Sandbox; store/TestFlight → Production) Match the APNs environment; re-register against it
2 Android delivery fails after working before FCM legacy key retired; app on v1, hub on legacy Google config shows a legacy server key, not a v1 service account Upload the FCM v1 service-account JSON; switch the hub to v1
3 “Unauthorized”/401 from the send call Backend used a Listen SAS, not Full Inspect the connection string’s rights Use the Full (Manage/Send) policy for sends
4 A tagged send reaches no one Tag never set at registration, or a typo List the installation and inspect its tags Set the correct tag; tags are case-sensitive
5 Delivery rate slowly falls over weeks Dead tokens accumulating (uninstalls) Outcome counts show a high “expired/invalid” share Use installation upserts + auto-expiry; stop re-adding stale handles
6 Duplicate notifications to one user Multiple registrations per device (no idempotent key) Same handle in several registrations Move to the installation model keyed by installationId
7 Payload rejected / truncated Native payload exceeds the PNS size limit (~4 KB) PNS error in the send outcome Shrink the payload; fetch large data on tap
8 Web push never arrives Missing/wrong VAPID keys, or unsupported browser Browser console; hub Web Push config Configure VAPID; verify browser support
9 Send “succeeds” but device never gets it Push is best-effort; PNS dropped it (device off/quota) Expected PNS behaviour — no hub fix Use push for alerts only; sync on app open
10 Throttled during a huge broadcast Sending faster than a PNS accepts 429/throttle in outcomes Let the hub batch (don’t loop); use Standard for scale
11 Some users on a tag get it, others don’t A stale handle the PNS rejects Outcome shows invalid handle for those devices Refresh handles on launch via installation upsert
12 Can’t push to a single user reliably Targeting by handle, not a stable identity No per-user tag exists Attach a user_<id> tag (or use installation userId)

The meta-rule: a success from the hub means “I accepted and forwarded it to the PNS,” not “the device received it.” When something is silent, the cause is almost always one hop downstream — wrong credential environment (rows 1–2), wrong access policy (row 3), or a targeting/tag error (rows 4, 12). Confirm the credential and the tags before suspecting the hub.

Best practices

Security notes

The hub’s security model is SAS access policies plus PNS credentials you must protect. Two SAS rights matter: Listen (a client registers its own device, nothing else) and Full (Manage + Send + Listen — your backend). Never embed a Full connection string in a mobile app; a leaked Full key lets anyone broadcast to your entire audience. Ship clients Listen only, and prefer minting short-lived, per-client SAS tokens from your backend over embedding any shared secret.

Concern Risk if ignored Control
Backend send key Leaked Full SAS → attacker broadcasts to all users Full policy stays server-side; rotate keys; per-client Listen tokens
PNS credentials Stolen APNs .p8/FCM JSON → impersonate your sender Store in Key Vault; least-privilege access; rotate
Client registration A rogue client tags itself into premium segments Validate/assign sensitive tags server-side, not from the client
PII in payloads Sensitive data on a lock screen / in PNS logs Keep payloads minimal; fetch details in-app after tap
Network exposure Management plane reachable broadly Use Entra-ID RBAC for control-plane ops; scope SAS narrowly
Auditing No record of who sent what Send diagnostic logs to a Log Analytics workspace

Two non-obvious points. First, tags are client-asserted unless you mediate them — if the app lets a device add tier_pro directly, a user can grant themselves premium pushes; assign entitlement tags from your backend after verifying the user. Second, payloads pass through Apple/Google infrastructure, so treat the notification body as visible on a locked screen and in provider telemetry — push the signal, fetch the secret in-app.

Cost & sizing

Notification Hubs pricing is per namespace tier plus pushes/month and extra active devices beyond the included quota. The three tiers — Free, Basic, Standard — differ in included pushes and devices, per-extra-device price, and feature gates (rich telemetry, scheduled and multi-tenant push). Free is genuinely useful for dev and small apps; Basic and Standard scale by paying mostly for additional devices and pushes.

Tier Best for Included pushes/mo (order of magnitude) Included active devices Notable feature gates
Free Dev, small apps ~1 million ~500 Core push; limited throughput
Basic Production, mid scale ~10 million ~200,000 Higher limits; per-extra-device charge
Standard Large/enterprise ~10 million base ~10 million Richer telemetry, scheduled push, multi-tenancy, higher SLAs

What drives the bill: the tier base fee, the active-device count above the included quota (the dominant cost at consumer scale), and pushes/month above the included pool. Rules of thumb: a hobby or internal app fits Free; a consumer app with a few hundred thousand devices fits Basic; millions of devices, frequent broadcasts, or a need for scheduled push and richer analytics make Standard the floor. Because device count dominates, prune dead registrations — auto-expiry plus the installation model do this for you, so target with tags, refresh handles on launch, and never pay for millions of uninstalled devices (the most common avoidable cost). Confirm current numbers on the Azure pricing page; tiers and quotas change.

Interview & exam questions

1. What is Azure Notification Hubs in one sentence? A managed service that takes a single backend send and fans it out as native push notifications to devices across APNs, FCM, Web Push, WNS, and ADM, handling per-platform translation, device registration, segmentation, and dead-token pruning. (Maps to AZ-204, AZ-305 messaging topics.)

2. How does Notification Hubs differ from Azure SignalR Service? Notification Hubs delivers OS-level push to a device even when the app is backgrounded or closed, via the platform’s PNS. SignalR pushes real-time messages over a persistent WebSocket to a client that is currently connected. Closed app → Notification Hubs; open app/live UI → SignalR.

3. What is a PNS, and why can’t your backend just push to the device? A Platform Notification System (APNs, FCM, Web Push, WNS) is the only entity a platform allows to deliver a push to its devices. Your backend must go through the PNS; Notification Hubs is the layer that holds the credentials and registrations and talks to each PNS for you.

4. Explain registration vs installation. Both are the hub’s record of a device (handle + tags + optional template). A registration is the older model with a server-assigned ID and is prone to duplicates. An installation is JSON keyed by a stable installationId you provide — idempotent upserts, patchable tags/templates — and is recommended for all new apps.

5. What is a tag, and how do you target a segment? A tag is a string label on a registration (country_IN, tier_pro). You target by sending to a tag or a tag expression ((country_IN && tier_pro)), and the hub delivers only to devices carrying matching tags — segmentation without your own token database.

6. Your iOS pushes return success but never arrive. First suspicion? An APNs Sandbox vs Production environment mismatch: the hub’s APNs credential environment must match how the app was signed (dev build → Sandbox, store/TestFlight → Production). The hub reports success because it forwarded to the PNS; the PNS dropped it.

7. What changed with FCM v1 and why does it matter here? Google retired the legacy FCM server-key API in favour of FCM v1, which uses a service-account JSON and OAuth2. A hub still configured with a legacy key fails to deliver to Android; you must upload the v1 service account and switch the hub to FCM v1.

8. When would you choose Event Grid or Service Bus over Notification Hubs? When the recipient is a service, not a device. Event Grid routes discrete system events (e.g. “blob created”) to subscribers; Service Bus carries durable, ordered messages/commands to backend consumers. A common pattern is Event Grid/Service Bus → Function → Notification Hubs.

9. Is push delivery guaranteed? No — push is best-effort. The PNS may drop a notification (device off, quota, throttling) with no per-device guarantee. For data that must arrive, reconcile state when the app next opens; never rely on push alone.

10. What’s the difference between Listen and Full SAS policies? Listen lets a client register its own device and nothing more — safe to expose. Full (Manage + Send + Listen) lets the holder broadcast and manage the hub — backend only. A leaked Full key lets anyone push to your entire audience.

11. How does Notification Hubs help you manage dead device tokens? When a PNS reports a token as unregistered (app uninstalled), the hub automatically expires that registration so future sends skip it — the feedback loop you would otherwise build per platform. Combined with installation upserts, this keeps your registration store (and bill) clean.

12. What is a template and when is it useful? A template is a payload skeleton with $(placeholders) registered per device. Your backend sends only values; the hub renders each device’s template, producing the correct shape per platform and per language from one send — ideal for multi-platform, localized notifications.

Quick check

  1. The app is closed and you must alert the user on the lock screen — Notification Hubs or SignalR?
  2. Your backend send returns 401 Unauthorized. What is the most likely cause?
  3. You send to the tag country_IN and nobody receives it, though devices are registered. Name two likely reasons.
  4. A success response from the hub guarantees the device received the push — true or false?
  5. Which device record model should new apps use, and what is its key benefit?

Answers

  1. Notification Hubs — it delivers OS-level push through the PNS to a backgrounded/closed app; SignalR only reaches a connected client.
  2. The backend used a Listen SAS policy instead of a Full (Manage/Send) policy. Listen can only register devices, not send.
  3. The tag was never set at registration on those devices, or there is a case/typo mismatch (country_in vs country_IN — tags are case-sensitive). A stale handle the PNS rejects is a third possibility.
  4. False. Success means the hub accepted and forwarded to the PNS; push is best-effort and the PNS may still drop it.
  5. The installation model, keyed by a stable installationId — idempotent upserts (no duplicate registrations) with patchable per-device tags and templates.

Glossary

Next steps

AzureNotification HubsPush NotificationsAPNsFCMMobileMessagingCross-Platform
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading