Skip to main content

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:

  1. Account creation — open the vendor account, get the credential set listed.
  2. Env vars — add to hmp-infrastructure/secrets/<env>.env (encrypted) and to the corresponding Kubernetes Secret (kubectl create secret generic <name> --from-env-file=...).
  3. File paths — drop any keys/certs into the listed paths inside the relevant container image (not in source).
  4. Post-deploy verification — run the listed curl/log check.
  5. 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)​

VarPurpose
EINVOICE_MOCKSet to false in prod (was true in dev/demo)
NIC_EINVOICE_URLProd URL after sandbox sign-off (NIC sends after vetting)
NIC_CLIENT_IDNIC GSTP client ID
NIC_CLIENT_SECRETNIC GSTP client secret
NIC_USERNAMENIC username
NIC_PASSWORDNIC password
EINVOICE_ENVsandbox 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)​

VarPurpose
SMS_MOCKSet to false in prod
TWILIO_ACCOUNT_SIDTwilio SID (TRAI compliant via Twilio India)
TWILIO_AUTH_TOKENTwilio token
TWILIO_PHONE_NUMBERSender ID (the 6-char DLT-approved header)
TRAI_DND_API_URLTRAI DND registry endpoint
TRAI_DND_API_KEYTRAI DND API key
DLT_TEMPLATE_ID_PRE_ARRIVALDLT-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)​

VarPurpose
RAZORPAY_KEY_IDLIVE key (was rzp_test_..., becomes rzp_live_...)
RAZORPAY_KEY_SECRETLIVE secret
RAZORPAY_WEBHOOK_SECRETSet 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_SECRET env

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:

RecordPurpose
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)​

VarPurpose
EMAIL_MOCKSet to false
SENDGRID_API_KEYSendGrid API key (full access)
SENDGRID_FROM_EMAILVerified sender (e.g. bookings@hotel.com)
SENDGRID_WEBHOOK_PUBLIC_KEYP1-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)​

VarPurpose
WHATSAPP_PHONE_NUMBER_IDFrom Meta Business Manager
WHATSAPP_ACCESS_TOKENSystem User permanent token
WHATSAPP_VERIFY_TOKENRandom string; used in webhook verification
WHATSAPP_BUSINESS_IDWABA ID for template management

Webhook config​

  • Callback URL: https://<gateway-public-host>/api/whatsapp/webhook
  • Verify Token: same as WHATSAPP_VERIFY_TOKEN env
  • 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:

ChannelAccountCredentials
Booking.comHotel adminUsername + password (XML rate plan API)
ExpediaEQC partnerAPI key + secret
AgodaYCS partnerHotel ID + API key
MakeMyTripMMT ConnectHotel code + API key
GoibiboGoStayHotel code + API key
TripAdvisorTA ListingListing ID + auth token
Google Hotel AdsGMB-linkedService 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.