Open Channel Middleware Workflow
This workflow guides an agent through building a complete proof-of-concept middleware server for Airship Open Channels. The resulting server handles all three Open Channels integration points: channel registration, webhook validation, and push delivery.
Overview
Open Channels let Airship deliver notifications to any platform with a webhook — WhatsApp, Slack, smart devices, custom apps, etc. Because there is no Airship SDK on these platforms, a middleware server is required to:
- Register channels — Accept new delivery addresses from your system and register them with Airship.
- Validate the webhook — Respond to Airship's validation handshake during dashboard setup.
- Receive and deliver pushes — Accept Airship's push payloads and route them to the actual delivery mechanism.
Your System ──POST /register──► Middleware ──POST /api/channels/open──► Airship
▲
Airship ──GET /airship/validate──► │
Airship ──POST /airship/push ──► │
└──► Your delivery mechanism (WhatsApp API, Slack, etc.)
Prerequisites
- Airship project with API access (app key + master secret)
- Airship dashboard access to configure the Open Channel and retrieve the validation code
- GCP project (for Cloud Run) or AWS account (for Lambda) with deployment access
- Node.js 18+ (examples below use Node.js; Python equivalents noted where useful)
Skills Required
- Open Channel Registration
- Named Users (optional, for named user association)
- Tags (optional, for post-registration tagging)
Phase 1: Discovery
Before generating any code, ask the customer these questions. Answers shape the generated output.
Platform name — What will you name this open channel platform? This becomes the
open_platform_namein Airship (e.g.,whatsapp,slack,alexa). Lowercase, no spaces.Address concept — What does the delivery address represent? (e.g., phone number, Slack user ID, device serial number). This shapes the inbound registration contract and documentation.
Deployment target — Where will this middleware run? GCP Cloud Run and AWS Lambda are common starting points and both have full implementations below, but any environment that can serve HTTP works — container platforms, VMs, existing Node.js/Python services, etc.
Language preference — Node.js or Python? (Node.js shown below; Python structure noted at end.)
Inbound auth — How should callers authenticate to the
/registerendpoint, and what value will they use? Options:- API key (recommended for PoC): Callers supply a secret in the
X-API-Keyheader. You'll need an actual value for this — either provide one or the agent can generate a random one for you. This becomes theINBOUND_API_KEYenvironment variable. - No auth (dev/testing only): Skip authentication entirely — remove the check before going anywhere near production.
- API key (recommended for PoC): Callers supply a secret in the
Named user association — Should registration automatically associate the channel with a named user? If yes, what field in the inbound request body carries the named user ID?
Delivery stub — For this PoC, the push delivery handler can be a stub that logs payloads (you wire up the real delivery SDK later). Or do you have a specific delivery API in mind?
Phase 2: Environment Variables
All sensitive values are injected via environment variables — never hardcoded.
| Variable | Required | Description |
|---|---|---|
AIRSHIP_APP_KEY |
Yes | Your Airship project app key |
AIRSHIP_MASTER_SECRET |
Yes (or OAuth) | Your Airship master secret — used for Basic auth |
AIRSHIP_CLIENT_ID |
Yes (or master secret) | OAuth client ID — preferred over master secret |
AIRSHIP_CLIENT_SECRET |
Yes (or master secret) | OAuth client secret |
AIRSHIP_OPEN_PLATFORM_NAME |
Yes | The open_platform_name configured in the dashboard |
AIRSHIP_VALIDATION_CODE |
Yes | The 36-character UUID from the Airship dashboard (after saving the open channel config) |
AIRSHIP_WEBHOOK_SECRET |
Yes (if using Signature Hash auth) | The secret key configured in the dashboard for X-UA-SIGNATURE verification |
INBOUND_API_KEY |
Recommended | API key callers must supply in X-API-Key to reach /register |
For Cloud Run: store secrets in GCP Secret Manager and expose as env vars in the Cloud Run service configuration. For Lambda: store secrets in AWS Secrets Manager or SSM Parameter Store and inject as Lambda environment variables.
Phase 3: Cloud Run Implementation (Node.js/Express)
The complete Cloud Run reference implementation (package.json, src/index.js, Dockerfile, .env.example, and deploy steps) is in references/cloudrun-nodejs.md.
Phase 4: Lambda Implementation (Node.js)
The complete AWS Lambda reference implementation (src/handler.js, template.yaml, and deploy steps) is in references/lambda-nodejs.md.
Phase 5: Airship Dashboard Setup
Do this after deploying the server (the dashboard requires a live URL).
- In the Airship dashboard, select the dropdown next to your project name → Settings.
- Under Channels, select Open Channels.
- Click + Configure new Open Channel and complete the form:
- Display Name: Human-friendly name (e.g., "WhatsApp PoC")
- Name: Your
open_platform_namevalue (e.g.,whatsapp) — must matchAIRSHIP_OPEN_PLATFORM_NAMEexactly. - Webhook URL: Your deployed service URL +
/airshipas the root (e.g.,https://your-service.run.app/airship). Airship will call/airship/validateand/airship/pushrelative to this. - Authentication: Select Signature Hash and enter a secret key → set this as
AIRSHIP_WEBHOOK_SECRETin your environment.
- Click Save. A Validation Code UUID appears — set this as
AIRSHIP_VALIDATION_CODEin your environment and redeploy/update the service. - Check the Enabled box and click Update. Airship will call
GET /airship/validate— it must return the validation code UUID.
Note: Airship re-validates the endpoint every time you update the configuration. The channel must be enabled for pushes to be delivered.
Phase 6: End-to-End Test
Step 1 — Verify validation endpoint
curl https://your-service/airship/validate
# Expected: {"confirmation_code":"<your-uuid>"}
Step 2 — Register a channel
curl -X POST https://your-service/register \
-H "Content-Type: application/json" \
-H "X-API-Key: your_inbound_api_key" \
-d '{
"address": "+15035556789",
"opt_in": true,
"named_user_id": "user_001",
"tags": ["test"]
}'
# Expected: {"ok":true,"channel_id":"<uuid>"}
Step 3 — Send a push via Airship API
curl -X POST https://api.asnapius.com/api/push \
-H "Authorization: Basic <base64(app_key:master_secret)>" \
-H "Content-Type: application/json" \
-H "Accept: application/vnd.urbanairship+json; version=3" \
-d '{
"audience": { "open_channel": "<channel_id from step 2>" },
"device_types": ["open::whatsapp"],
"notification": { "alert": "Hello from Airship!" }
}'
Step 4 — Verify delivery log
Check your service logs for the delivery log entry:
- Cloud Run:
gcloud run services logs read open-channel-middleware - Lambda: CloudWatch Logs →
/aws/lambda/open-channel-middleware
Expected log output:
{
"send_id": "<uuid>",
"address": "+15035556789",
"alert": "Hello from Airship!"
}
Python Notes
If the customer prefers Python, the same structure applies using FastAPI (Cloud Run) or the standard Lambda handler pattern.
Key equivalents:
- Gzip decompression:
import gzip; body = gzip.decompress(raw_bytes) - HMAC-SHA256:
import hmac, hashlib; hmac.new(secret.encode(), message, hashlib.sha256).hexdigest() - Constant-time compare:
hmac.compare_digest(a, b) - Airship API calls:
httpx.AsyncClientorrequests
Production Considerations
This workflow produces a proof of concept. Before going to production, consider:
- Async delivery: The push delivery handler should return 200 immediately and process sends asynchronously (Cloud Tasks, SQS, etc.) to avoid Airship retry storms on slow downstream APIs.
- Idempotency: Use
send_idfor deduplication — Airship may retry delivery on non-2xx responses. - Batching: Airship delivers up to 1,000 send objects per request. Batch your downstream API calls accordingly.
- Monitoring: Add structured logging and alerting on 502 errors from Airship registration.
- Secrets rotation: Use Secret Manager / Secrets Manager with automatic rotation rather than static env vars.