Vendor Onboarding Runbook
Date: 2026-04-26
Audience: Ops lead + 1 ops engineer
Tracks: P0-9 (NIC), P0-13 (PCI), P0-17 (Razorpay), P0-20 (Email + SMS), C-1..C-8 in golive-implementation-plan-2026-04-25.md
How to use this doc
For each vendor section:
- Account creation — open the vendor account, get the credential set listed.
- Env vars — add to
hmp-infrastructure/secrets/<env>.env(encrypted) and to the corresponding Kubernetes Secret (kubectl create secret generic <name> --from-env-file=...). - File paths — drop any keys/certs into the listed paths inside the relevant container image (not in source).
- Post-deploy verification — run the listed curl/log check.
- Done — flip the row to ✅ in
golive-implementation-plan-2026-04-25.md§ Status Dashboard.
The lead times are firm. Start the long-lead items (TRAI DLT, PCI QSA, WhatsApp Business) on day 1 — they will be the production-launch critical path.
C-1 — NIC e-invoice production (closes P0-9)
Lead time: 2-3 weeks (NIC vetting, sandbox sign-off, prod activation)
Accounts + GSTIN
- One GSTP login per filing entity. Apply at einvoice1.gst.gov.in.
- Each property's GSTIN (15-char) must already be registered.
- Sandbox vs production are separate creds.
Env vars (per service: hmp-frontdesk-service)
| Var | Purpose |
|---|---|
EINVOICE_MOCK | Set to false in prod (was true in dev/demo) |
NIC_EINVOICE_URL | Prod URL after sandbox sign-off (NIC sends after vetting) |
NIC_CLIENT_ID | NIC GSTP client ID |
NIC_CLIENT_SECRET | NIC GSTP client secret |
NIC_USERNAME | NIC username |
NIC_PASSWORD | NIC password |
EINVOICE_ENV | sandbox while testing, production for live |
Verification (after deploy)
# 1. Create a B2B folio with company GSTIN, settle it.
# 2. Trigger IRN generation:
curl -X POST $GW/api/einvoice/generate \
-H "Authorization: Bearer $TOKEN" \
-H "x-property-id: $PROP" \
-d '{"folioId":"$FOLIO"}'
# 3. Poll job status until COMPLETED:
curl $GW/api/einvoice/jobs?status=COMPLETED -H "Authorization: Bearer $TOKEN"
# 4. Confirm IRN is a real 64-char hex (not the mock SHA-256 echo).
# 5. Verify on https://einvoice1.gst.gov.in/Others/SearchIRN with the IRN.
Compliance dashboard
After credentials land, run the new GST e-invoice compliance report (P0-16) for the previous day:
GET /api/einvoice/compliance-report?fromDate=YYYY-MM-DD&toDate=YYYY-MM-DD
Verify zero rows in the missing bucket on a quiet day.
C-2 — TRAI DLT (Distributed Ledger) for SMS (closes part of P0-20)
Lead time: 2-4 weeks (DLT registration with Vodafone Idea / Airtel / Jio, Sender ID approval, template approval per content type)
Accounts
- One DLT account per registered entity (typically the chain holder).
- Sender ID (header): 6 chars, alphanumeric — request via DLT portal.
- Each SMS template must be registered + approved before send.
Env vars (per service: hmp-notification-service)
| Var | Purpose |
|---|---|
SMS_MOCK | Set to false in prod |
TWILIO_ACCOUNT_SID | Twilio SID (TRAI compliant via Twilio India) |
TWILIO_AUTH_TOKEN | Twilio token |
TWILIO_PHONE_NUMBER | Sender ID (the 6-char DLT-approved header) |
TRAI_DND_API_URL | TRAI DND registry endpoint |
TRAI_DND_API_KEY | TRAI DND API key |
DLT_TEMPLATE_ID_PRE_ARRIVAL | DLT-approved template ID for pre-arrival SMS |
DLT_TEMPLATE_ID_BOOKING_CONFIRM | … |
DLT_TEMPLATE_ID_FEEDBACK | … |
Templates to register (one per content type)
Source: hmp-notification-service/src/templates/ (and dashboard /settings/templates). Take each prod-shipping template, submit DLT via the operator portal. Approval is per-template, takes 2-5 business days.
Verification
# Send a test confirmation, then check the SMS log:
curl $GW/api/notifications?channel=SMS&limit=1 -H "Authorization: Bearer $TOKEN"
# Expect: status=DELIVERED + providerMsgId starts with the DLT-approved sender ID.
C-3 — Razorpay LIVE (closes P0-17)
Lead time: ~1 week (KYC + activation, settlement-account verification)
Account
- Razorpay business account (Standard or Enterprise tier).
- KYC: PAN, GSTIN, current account, owner Aadhaar.
- LIVE keys are issued only after settlement-account is verified by Razorpay (1-3 days post-KYC).
Env vars (per service: hmp-reservation-service + hmp-payment-service)
| Var | Purpose |
|---|---|
RAZORPAY_KEY_ID | LIVE key (was rzp_test_..., becomes rzp_live_...) |
RAZORPAY_KEY_SECRET | LIVE secret |
RAZORPAY_WEBHOOK_SECRET | Set to a random 64-char string, register the same value in Razorpay dashboard → Settings → Webhooks |
Webhook config (in Razorpay dashboard)
- URL:
https://<gateway-public-host>/api/webhooks/razorpay-gateway/<merchantAccountId> - Events:
payment.authorized,payment.captured,payment.failed,refund.processed,refund.failed - Active: yes
- Secret: same as
RAZORPAY_WEBHOOK_SECRETenv
Verification
# 1. Initiate a small (₹1) test payment through booking-engine.
# 2. Complete in Razorpay LIVE checkout.
# 3. Confirm folio.balance = 0 + Payment row created with mode=RAZORPAY + status=COMPLETED.
# 4. Issue a ₹1 refund:
curl -X POST $GW/api/payments/refund -H "Authorization: Bearer $TOKEN" \
-d '{"paymentId":"<id>","amount":1}'
# 5. Verify refund.processed webhook landed in webhook_events table.
C-4 — SendGrid domain + DKIM (closes part of P0-20)
Lead time: 3-5 business days (DNS propagation + DKIM verification)
Account
- SendGrid Pro tier (Essential plan won't cover the volume).
- One verified sender per property OR one chain-level sender (
bookings@hotelchain.com) — recommend chain-level to reduce DNS overhead. - Set up dedicated IP if outbound volume > 100k/mo (improves deliverability).
DNS records to add
SendGrid issues these via the dashboard. Add to your DNS provider:
| Record | Purpose |
|---|---|
s1._domainkey.<domain> (CNAME) | DKIM signature (SendGrid host) |
s2._domainkey.<domain> (CNAME) | DKIM signature backup |
<unique>.<domain> (CNAME) | Bounce + click tracking subdomain |
TXT _dmarc.<domain> | DMARC policy: v=DMARC1; p=quarantine; rua=mailto:dmarc@<domain> |
Env vars (per service: hmp-notification-service)
| Var | Purpose |
|---|---|
EMAIL_MOCK | Set to false |
SENDGRID_API_KEY | SendGrid API key (full access) |
SENDGRID_FROM_EMAIL | Verified sender (e.g. bookings@hotel.com) |
SENDGRID_WEBHOOK_PUBLIC_KEY | P1-C2 — base64-encoded ED25519 SPKI key for the SendGrid Event Webhook (Settings → Mail Settings → Event Webhook → Signed Event Webhook → copy public key) |
Webhook config (in SendGrid dashboard)
- HTTP POST URL:
https://<gateway-public-host>/api/email/webhook/sendgrid - Select events:
Delivered,Open,Click,Bounce,Dropped,Spam Report,Unsubscribe,Group Unsubscribe - Enable signed event webhook: yes (this is what the public key is for)
Verification
# Send a test confirmation:
curl -X POST $GW/api/reservations -H "Authorization: Bearer $TOKEN" -d '{...}'
# Then poll the notification log:
curl $GW/api/notifications?channel=EMAIL&limit=1 -H "Authorization: Bearer $TOKEN"
# Expect: status=DELIVERED + provider=sendgrid + a real sg_message_id (not msg-XXXX from the mock).
C-5 — Twilio + DLT for SMS (closes part of P0-20)
Sub-task of C-2. After DLT sender ID + template approvals land, switch SMS_MOCK to false and confirm Twilio is sending via the DLT-approved sender. Same verification curl as C-2.
C-6 — WhatsApp Business API (Phase 2)
Lead time: 3-4 weeks (Meta Business verification + display name + template approval)
Account
- Meta Business Manager account
- WhatsApp Business Account (WABA) under it
- Display name approved (Meta has a strict naming policy)
- Phone number (cannot be re-used between Meta and Twilio WhatsApp)
Env vars (per service: hmp-notification-service)
| Var | Purpose |
|---|---|
WHATSAPP_PHONE_NUMBER_ID | From Meta Business Manager |
WHATSAPP_ACCESS_TOKEN | System User permanent token |
WHATSAPP_VERIFY_TOKEN | Random string; used in webhook verification |
WHATSAPP_BUSINESS_ID | WABA ID for template management |
Webhook config
- Callback URL:
https://<gateway-public-host>/api/whatsapp/webhook - Verify Token: same as
WHATSAPP_VERIFY_TOKENenv - Subscribe to:
messages,message_status,messaging_handovers
Verification
Send the "checkin_ready" template via dashboard → Settings → WhatsApp → Send Test. Inbound replies should show in /whatsapp/conversations/:guestId.
C-7 — PCI DSS QSA (closes P0-13)
Lead time: 4 weeks (engagement + assessment + remediation cycle)
Engagement
- Engage a PCI Qualified Security Assessor (QSA). India-region options: NSEIT, KPMG, Deloitte, EY.
- Scope question: payment-service is the only PCI-relevant micro. Confirm in scope-narrowing meeting.
- Document SAQ-A if the merchant is fully outsourced (Razorpay-hosted) — this is the cheapest path.
- Document SAQ-D-merchant if any cardholder data ever touches the platform — this is the harder path. We do NOT touch card data per PCI standards (Razorpay tokenizes), so SAQ-A is the correct answer.
Quarterly ASV scan
Engage an Approved Scanning Vendor (ASV) — Qualys, Tenable, Trustwave. They scan the public surface of the gateway + payment-service quarterly. Reports go to Razorpay annually.
Verification
QSA issues an Attestation of Compliance (AoC). File it with:
- Razorpay (per their merchant agreement)
- Internal compliance vault (
hmp-infrastructure/compliance/pci-aoc-2026.pdf)
C-8 — OTA auth per property (closes P0-14 / P0-15 production)
Lead time: 1-2 weeks per property (Booking.com + Expedia + Agoda each have separate KYB)
Per-property credential matrix
For every property, ops must obtain:
| Channel | Account | Credentials |
|---|---|---|
| Booking.com | Hotel admin | Username + password (XML rate plan API) |
| Expedia | EQC partner | API key + secret |
| Agoda | YCS partner | Hotel ID + API key |
| MakeMyTrip | MMT Connect | Hotel code + API key |
| Goibibo | GoStay | Hotel code + API key |
| TripAdvisor | TA Listing | Listing ID + auth token |
| Google Hotel Ads | GMB-linked | Service account JSON |
Loaded into ChannelConnection table per property, NOT into env vars (multi-property + per-property credential rotation).
Webhook config
- ResAvenue (intermediary): URL =
https://<gateway-public-host>/api/webhooks/resavenue/reservation - Booking.com direct webhook (when on direct contract): URL =
https://<gateway-public-host>/api/webhooks/booking-com/reservation(this controller is TODO — not yet implemented; ResAvenue covers it) - Per-channel signature verification keys go into
IntermediaryConnection.credentials.signatureSecret
Verification (per channel, per property)
# 1. Trigger a synthetic OTA booking from the channel's sandbox.
# 2. Confirm reservation lands:
curl $GW/api/reservations?source=CHANNEL&limit=1 -H "Authorization: Bearer $TOKEN"
# 3. Confirm webhook_events row with status=ACCEPTED:
docker exec docker-postgres-channel-1 psql -U hmp -d hmp_channel \
-c "SELECT status, eventType FROM webhook_events ORDER BY createdAt DESC LIMIT 5;"
# 4. Confirm channel-booking-received consumer ingested it:
curl $GW/api/reservations?sourceReference=<otaBookingId> -H "Authorization: Bearer $TOKEN"
Status tracking
After each vendor item completes, edit golive-implementation-plan-2026-04-25.md § Status Dashboard. Flip the corresponding ❌ to ✅.
The flow-test suite (hmp-infrastructure/testing/flow-tests/) has scenarios for SendGrid, Twilio, NIC, Razorpay, and OTA inbound — they will start passing as each vendor flips on. Run npm run flow:test against the production-mirror environment after each vendor lands; soft-skip warnings turn into green test cases.