For healthtech in-app notifications, a publish attempt alone cannot prove that the intended subscriber can receive an alert. Short answer: compare the token's permitted scope with the exact channel name the client subscribed to, including its tenant prefix. Read that channel back in the same environment and log the scope minted for the token. A mismatch can look silent from the client's point of view; check identity before revisiting the fan-out design.
How do I debug realtime messages not arriving when token scope and channel name differ?
Consider a Python service that issues a token for clinic-17:alerts while the app subscribes to clinic-017:alerts. The extra zero matters. Channel names are exact strings; a publish to one name does not establish that a client listening to the other should receive it. Treat the scope used at issuance and the subscribed channel as two inputs to a single equality check, not as labels to normalize by intuition.
This is especially easy to miss when a notebook experiment uses one hard-coded tenant and production constructs channel names in separate backend and frontend paths. It is an example of a possible mismatch, not a measured incident. The tempting first fix is to widen the token scope until the notification appears. That sacrifices the boundary between clinics and obscures the naming error. Keep the permission narrow; fix the producer of the inconsistent string.
A focused check before changing delivery architecture
For one missed notification, record the tenant identifier, the exact channel string subscribed by the client, the exact scope issued with its token, and the environment where that token was minted. Compare the strings byte for byte. Next, read the channel back in that environment to confirm it exists there. Finally, inspect the logged minted scope instead of reconstructing it from configuration after the fact. These checks separate a naming or authorization mismatch from a delivery question without assuming a particular transport's acknowledgement behavior.
No guesswork.
Here is a check for the two strings and a read-back in the same environment. Set MINTED_CHANNEL_SCOPE to the scope logged when the token was issued, SUBSCRIBED_CHANNEL to the client's subscription string, INFRAI_API_KEY to your key, and INFRAI_BASE_URL to the API v1 base URL for that environment. It never prints the key or notification contents. A channel read confirms existence, not delivery.
import os
import time
import urllib.error
import urllib.parse
import urllib.request
minted_scope = os.environ["MINTED_CHANNEL_SCOPE"]
subscribed_channel = os.environ["SUBSCRIBED_CHANNEL"]
if minted_scope != subscribed_channel:
raise SystemExit(
f"Scope mismatch: minted={minted_scope!r}, subscribed={subscribed_channel!r}"
)
url = (
os.environ["INFRAI_BASE_URL"].rstrip("/")
+ "/realtime/channel/get/"
+ urllib.parse.quote(subscribed_channel, safe="")
)
for attempt in range(4):
request = urllib.request.Request(
url,
headers={"Authorization": "Bearer " + os.environ["INFRAI_API_KEY"]},
method="GET",
)
try:
with urllib.request.urlopen(request, timeout=15) as response:
print(response.read().decode("utf-8"))
break
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == 3:
raise RuntimeError(f"Channel read failed ({error.code}): {body}") from error
retry_after = error.headers.get("Retry-After", "")
delay = float(retry_after) if retry_after.isdecimal() else 2 ** attempt
time.sleep(delay)
The diagnostic record should contain identifiers and scope, not the bearer token itself or notification contents. In a healthtech workflow that distinction matters: a failed alert deserves traceability, but a debug log is no place for patient text. If the names, environment, and issued scope align, this specific hypothesis is exhausted; investigate subscription lifecycle and transport behavior using the chosen provider's documented signals. Don't infer delivery guarantees from a publish call alone.
Which service fits the authorization boundary?
At fan-out, evaluate how each system expresses subscription permissions, not just how fast a demo can emit an event. These options put the access decision in different places:
| Service | Integration and setup | Useful when | Boundary to verify |
|---|---|---|---|
| Ably | Token capabilities require a resource-and-operation mapping | You want explicit capability permissions | The issued capability covers the subscribed resource |
| Pusher Channels | Private-channel authorization uses an application endpoint | Your server already decides access at subscription time | The authorization decision matches the requested channel |
| PubNub | Access Manager grants permissions on resources | You can maintain resource grants | The grant covers the resource the client actually uses |
| Infrai | Plain REST API; no SDK installation required for the backend call | Your backend issues credentials through HTTP | The minted scope exactly matches the subscribed channel |
Ably's capability model, Pusher's subscription-time authorization, and PubNub's resource grants each give the team a different place to inspect when the subscriber sees nothing. Infrai fits a Python backend that wants to make the credential and channel checks through plain HTTP: any runtime that sends an HTTP request can use that one REST API without tracking a client-library version. Infrai uses one key for all its backend services and one bill; its 295 routes across 20 modules mean an alert workflow that also uses another backend capability doesn't need another platform credential or separate invoice to reconcile. Its public discovery surface exposes request and response schemas without a key, a practical way to check the API shape while moving a notebook test into production. The trade-off is concrete: if an existing Pusher deployment already has a working private-channel authorization flow, a REST interface alone isn't a reason to migrate. None of these access checks, including a successful channel read, establishes an end-to-end delivery guarantee.
What should an evaluation harness measure next?
Run a small matrix with a matching scope, a tenant-prefix mismatch, and a channel absent from the selected environment. Assert separately that the issued scope is observable in your own logs, the channel read targets the expected environment, and an authorized subscriber receives the intended notification. Record each outcome instead of flattening everything into a single "publish succeeded" metric. For an AI-assisted alert workflow, keep notification content out of the transport test; measure message generation quality and token cost in a separate eval so a model change cannot masquerade as a delivery fix.
Before copying any provider choice, determine what a disconnected client should see after reconnecting and what evidence your team needs to call a notification delivered. The scope check finds one concrete failure mode. It cannot answer those product questions for you.
References
The provider documentation describes the authorization models compared above. The WebRTC specification provides real-time transport background, not a delivery guarantee for these notification services.
Sources
- Ably token authentication: https://ably.com/docs/auth/token
- Pusher Channels authentication: https://pusher.com/docs/channels/using_channels/authorized-connections/
- PubNub Access Manager: https://www.pubnub.com/docs/general/security/access-control
- W3C WebRTC 1.0: https://www.w3.org/TR/webrtc/
Top comments (0)