AI-Assisted Integration Scaffold
Copy the prompt below into any AI assistant (ChatGPT, Claude, Cursor, Gemini, etc.), fill in the three placeholders at the top, and the AI will generate a complete, production-ready integration scaffold — backend notification service, frontend WebSocket client, notification bell component, and retry logic — in one shot.
I need to integrate my application with the Orqestra Notification Engine.
Fill in the values below, then generate everything listed under "Deliverables".
─── FILL THESE IN ────────────────────────────────────────────────────────────
Language / Framework : [e.g. Node.js/Express, Python/FastAPI, Go/Gin, Ruby/Rails]
Orqestra Host URL : [e.g. https://notifications.mycompany.com]
My Tenant ID : [provided by the platform team — used in history API URLs]
─── END OF CONFIG ────────────────────────────────────────────────────────────
Read the full API reference at the bottom before generating code.
═══════════════════════════════════════════════════════════════════════════════
DELIVERABLES
═══════════════════════════════════════════════════════════════════════════════
1. BACKEND — Notification Service
A reusable module / class with:
a) sendNotification(eventType, payload, variables?, options?) function
- payload: { userId, recipientEmail?, recipientPhone? } — routing identity only
- variables: Record<string, unknown> — template data interpolated at render time
- Reads ORQESTRA_API_KEY from environment
- Accepts an optional idempotencyKey in options; when provided, sends it
as the X-Idempotency-Key header to prevent duplicate processing on retries
- Accepts an optional channels string[] in options to restrict delivery
(e.g. ["EMAIL"] fires email only for this single call)
b) HMAC-SHA256 request signing
- If ORQESTRA_WEBHOOK_SECRET env var is set, compute
HMAC-SHA256(rawJsonBody, secret) and send the hex result in
X-Orqestra-Signature header
- If the env var is absent, omit the header silently
c) Robust error handling
- 401 → log "Invalid API key or inactive tenant", do not retry
- 422 → log the validation errors array from the response body, do not retry
- 429 → read the retryAfter field from the JSON body (seconds), wait that
duration, then retry the same request (max 3 retries total)
- 5xx → exponential backoff retry: 2s, 8s, 30s (max 3 retries)
d) Usage examples for:
- New user registration → eventType "user.registered"
- Order confirmation → eventType "order.confirmed"
- Urgent broadcast → eventType "global.alert", channels: ["SMS"]
- Password reset email → eventType "global.alert", channels: ["EMAIL"]
2. FRONTEND — Real-Time WebSocket Client
A self-contained module that:
a) requestRealtimeToken(userId)
- POST /api/v1/auth/realtime-token with x-api-key header and { userId }
- Returns { token: string, channels: string[] }
b) connectRealtime(userId, onNotification)
- Calls requestRealtimeToken, then connects to the Centrifugo WebSocket
at wss://<host>/connection/websocket using the centrifuge-js library
- Subscribes to all channels in the returned array
- On every publication, calls onNotification(data) where data has:
{ title: string, body: string, category: string, eventType: string }
- Implements token refresh using Centrifugo's getToken callback (2h expiry)
- Does not disable autoReconnect
c) disconnectRealtime()
- Calls centrifuge.disconnect()
3. FRONTEND — Notification Bell Component
A UI component that:
a) On mount, fetches notification history from:
GET /api/v1/notifications/{tenantId}/{userId}
Headers: x-api-key
b) Displays a bell icon with an unread count badge (UNREAD items)
c) On click, opens a dropdown listing notifications with:
- title, body from history response
- Human-readable relative timestamp ("3 minutes ago")
- Visual distinction between UNREAD and READ
d) On clicking a notification, marks it read via:
PUT /api/v1/notifications/{tenantId}/{userId}/{notificationId}/read
Headers: x-api-key
Updates the item status locally without re-fetching the full list
e) Prepends new real-time notifications from Deliverable 2
═══════════════════════════════════════════════════════════════════════════════
COMPLETE API REFERENCE
═══════════════════════════════════════════════════════════════════════════════
── Trigger Event ─────────────────────────────────────────────────────────────
POST /api/v1/events/trigger
Headers:
Content-Type: application/json
x-api-key: <ORQESTRA_API_KEY>
X-Orqestra-Signature: <hmac-sha256-hex> (required if webhook secret configured)
X-Idempotency-Key: <unique-string> (optional; prevents duplicate processing)
X-Trace-Id: <trace-id> (optional; for distributed tracing)
Body:
{
"eventType": string, // required — e.g. "order.confirmed", "global.alert"
"eventId": string, // optional — auto-generated if omitted
"channels": string[], // optional — ["EMAIL"|"SMS"|"PUSH"] restricts this call only
"payload": {
"userId": string, // required — recipient's user ID in your system
"recipientEmail": string, // required for EMAIL channel
"recipientPhone": string, // required for SMS channel (E.164 format: +254712345678)
},
"variables": {
// optional — arbitrary key/value pairs available as {{fieldName}} in templates
// shape depends entirely on what the template expects — no reserved keys
// for global.* event types, include at least: title (string), message (string)
"title": string, // required for global.* event types
"message": string, // required for global.* event types
// "orderNumber": string, "amount": string, ... (any custom fields)
}
}
Success response (200):
{ "success": true, "message": "Event <eventType> dispatched securely for <TenantName>" }
Error responses:
401 → { "statusCode": 401, "message": "Invalid or inactive API key", "error": "Unauthorized" }
422 → { "statusCode": 400, "message": ["eventType must be a string", ...], "error": "Bad Request" }
429 → { "statusCode": 429, "error": "Too Many Requests",
"message": "Rate limit exceeded. Limit: 100/min", "retryAfter": 45 }
── Request Realtime Token ────────────────────────────────────────────────────
POST /api/v1/auth/realtime-token
Headers: Content-Type: application/json, x-api-key: <ORQESTRA_API_KEY>
Body: { "userId": string }
Response: { "token": string, "channels": string[] }
Tokens expire after 2 hours. Refresh using Centrifuge's getToken callback.
── WebSocket Connection ──────────────────────────────────────────────────────
Endpoint : wss://<host>/connection/websocket
Library : centrifuge-js (npm install centrifuge)
Publication payload shape:
{ "title": string, "body": string, "category": string, "eventType": string }
── Notification History ──────────────────────────────────────────────────────
GET /api/v1/notifications/{tenantId}/{userId}
Headers: x-api-key
Response: { "success": true, "data": [{ "id", "type", "title", "body", "status": "UNREAD"|"READ", "created_at" }] }
── Mark Read ─────────────────────────────────────────────────────────────────
PUT /api/v1/notifications/{tenantId}/{userId}/{notificationId}/read
Headers: x-api-key
Response: { "success": true }
── HMAC Signing ──────────────────────────────────────────────────────────────
algorithm : HMAC-SHA256
input : raw JSON request body string (before any parsing)
key : ORQESTRA_WEBHOOK_SECRET environment variable
output : lowercase hex digest → send in X-Orqestra-Signature header
═══════════════════════════════════════════════════════════════════════════════
PRE-FLIGHT — MUST BE DONE IN THE ADMIN UI BEFORE CODE WILL WORK
═══════════════════════════════════════════════════════════════════════════════
Include a clearly formatted warning comment in the generated output:
Step 1 — Configure a delivery provider in Admin UI → Providers
Step 2 — Create and activate a notification template in Admin UI → Templates
(template eventType must exactly match the string you pass to sendNotification)
Step 3 — Test with eventType "global.info" + variables { title, message } first
═══════════════════════════════════════════════════════════════════════════════
CONSTRAINTS
═══════════════════════════════════════════════════════════════════════════════
- Store ORQESTRA_API_KEY, ORQESTRA_WEBHOOK_SECRET, and tenantId as environment
variables — never hardcode or expose client-side
- Use the project's existing HTTP client and component patterns
- Do not introduce new dependencies unless centrifuge-js is absent