Kulcare Docs
UhiHspa

HSPA — Provider Guide

When Kulcare acts as an HSPA (Health Service Provider Application), it receives requests from EUAs (patient apps on the UHI network) and responds by building doctor catalogs, holding slots, generating PINs, and managing the full booking lifecycle.

All HSPA endpoints live at /uhi/* and use Uhi::UhiController as the base — they verify the incoming UHI Authorization header rather than a Kulcare JWT.


HSPA Endpoint Summary

EndpointCallerPurpose
POST /uhi/searchUHI Gateway (broadcast)Receive search, return our doctor catalog
POST /uhi/initEUA (P2P)Receive booking request, return T&C
POST /uhi/confirmEUA (P2P)Confirm booking, generate PIN
POST /uhi/statusEUA (P2P)Return current appointment state
POST /uhi/cancelEUA (P2P)Process cancellation, issue refund

Full HSPA Lifecycle


HSPA Signing

All outbound calls from our HSPA role are signed with the HSPA key pair (not the EUA key pair):

auth = Uhi::AuthService.new(service_type: :physical, role: :hspa)
header = auth.generate_header(http_method: "POST", endpoint: "/on_confirm", body: payload.to_json)

Credentials path: uhi.tele_physical_consultation.hspa.{subscriber_id, public_key_id, private_key}


POST /uhi/search — Receive Broadcast Search

Caller: UHI Gateway (fan-out from EUA broadcast)

{
  "context": {
    "domain":       "nic2004:85111",
    "action":       "search",
    "consumer_id":  "nha.eua",
    "consumer_uri": "https://eua-app.example.com/uhi",
    "transaction_id": "txn-abc123"
  },
  "message": {
    "intent": {
      "category":    { "descriptor": { "code": "CARDIOLOGY" } },
      "fulfillment": { "type": "Physical" },
      "location":    { "city": { "code": "011" } }
    }
  }
}

Response: 200 ACK (immediately)

Then (async): Uhi::CatalogService sends on_search to consumer_uri with matching doctors.

Doctor Catalog Format (on_search payload)

{
  "context": {
    "action":       "on_search",
    "provider_id":  "kulcare.hspa",
    "provider_uri": "https://api-stg.kulcare.com/hspa",
    "consumer_uri": "https://eua-app.example.com/uhi",
    "transaction_id": "txn-abc123"
  },
  "message": {
    "catalog": {
      "descriptor": { "name": "Kulcare Health Network" },
      "providers": [
        {
          "id": "clinic-123",
          "descriptor": { "name": "City Heart Clinic" },
          "categories": [
            { "id": "cat-1", "descriptor": { "code": "CARDIOLOGY", "name": "Cardiology" } }
          ],
          "fulfillments": [
            {
              "id":    "slot-uuid-001",
              "type":  "Physical",
              "agent": { "id": "drgupta@hpr.ndhm", "name": "Dr. Gupta" },
              "start": { "time": { "timestamp": "2026-04-17T09:00:00" } },
              "end":   { "time": { "timestamp": "2026-04-17T09:20:00" } }
            }
          ],
          "items": [
            { "id": "0", "price": { "currency": "INR", "value": "600.0" } }
          ]
        }
      ]
    }
  }
}

POST /uhi/init — Receive Booking Request

Caller: EUA (direct P2P)

Receive patient details and selected slot. The controller:

  1. Generates an order_id (MMDD-<hex> format)
  2. Temporarily holds the slot
  3. Returns on_init with 5 standard terms

Five T&C Terms sent in on_init:

[
  { "type": "Commercial",   "descriptor": { "name": "Commercial Terms" },    "termsState": "INITIATED" },
  { "type": "Cancellation", "descriptor": { "name": "Cancellation Policy" }, "termsState": "INITIATED", "reasonRequired": true },
  { "type": "Refund",       "descriptor": { "name": "Refund Policy" },       "termsState": "INITIATED" },
  { "type": "Payment",      "descriptor": { "name": "Payment Terms" },       "termsState": "INITIATED" },
  { "type": "Settlement",   "descriptor": { "name": "Settlement Terms" },    "termsState": "INITIATED" }
]

POST /uhi/confirm — Confirm and Issue PIN

Caller: EUA (direct P2P)

On receiving confirmation (all terms AGREED):

  1. Validates all 5 terms are present with termsState: "AGREED"
  2. Generates a 4-digit PIN
  3. Sends on_confirm to EUA with PIN and validity window
  4. Sends audit copy to UHI Gateway

PIN validity: Full booking date (00:00 → 23:59 UTC)

pin = rand(1000..9999).to_s
valid_from = Time.now.utc.beginning_of_day.iso8601
valid_to   = Time.now.utc.end_of_day.iso8601

POST /uhi/status — Return Current State

Caller: EUA (direct P2P)

Look up the appointment in our database and return current state via on_status.

TODO (Phase 4 data layer): Current implementation returns "CONFIRMED" as placeholder. Wire to real appointment record.


POST /uhi/cancel — Process Cancellation

Caller: EUA (direct P2P)

  1. Extract @abdm/gov.in/cancelledby tag from the order fulfillment
  2. Apply the appropriate cancellation policy
  3. Update appointment to CANCELLED
  4. Send on_cancel with payment.status: "REFUND_INITIATED" if applicable
  5. Send audit copy to UHI Gateway

Pushing State Updates (HSPA → EUA)

The HSPA can proactively push state changes to the EUA at any time using on_update. This is done by calling the EUA's consumer_uri/on_update:

auth = Uhi::AuthService.new(service_type: :physical, role: :hspa)
payload = {
  context: { action: "on_update", transaction_id: "txn-abc123", ... },
  message: { order: { id: "0415-xxx", state: "APPOINTMENT_STARTED" } }
}
header = auth.generate_header(http_method: "POST", endpoint: "/on_update", body: payload.to_json)
Net::HTTP.post(URI("#{consumer_uri}/on_update"), payload.to_json, "Authorization" => header)

Valid states the HSPA can push: APPOINTMENT_STARTED, COMPLETED, NO_SHOW, CANCELLED, REFUND_INITIATED, HSPA_OVERRIDE.


CORS / Incoming Request Notes

HSPA endpoints (/uhi/*) do not require Kulcare JWT authentication. They verify the UHI Authorization header from the sender. In development and test environments, signature verification is skipped to simplify sandbox testing.

To allow the UHI Gateway to call /uhi/*, the host api-stg.kulcare.com must be registered in NHA's Network Registry with:

  • subscriber_id: kulcare.hspa
  • public_key_id: kulcare.hspapid
  • public_key: the Ed25519 public key matching uhi.tele_physical_consultation.hspa.private_key

On this page