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
| Endpoint | Caller | Purpose |
|---|---|---|
POST /uhi/search | UHI Gateway (broadcast) | Receive search, return our doctor catalog |
POST /uhi/init | EUA (P2P) | Receive booking request, return T&C |
POST /uhi/confirm | EUA (P2P) | Confirm booking, generate PIN |
POST /uhi/status | EUA (P2P) | Return current appointment state |
POST /uhi/cancel | EUA (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:
- Generates an
order_id(MMDD-<hex>format) - Temporarily holds the slot
- Returns
on_initwith 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):
- Validates all 5 terms are present with
termsState: "AGREED" - Generates a 4-digit PIN
- Sends
on_confirmto EUA with PIN and validity window - 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.iso8601POST /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)
- Extract
@abdm/gov.in/cancelledbytag from the order fulfillment - Apply the appropriate cancellation policy
- Update appointment to
CANCELLED - Send
on_cancelwithpayment.status: "REFUND_INITIATED"if applicable - 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.hspapublic_key_id:kulcare.hspapidpublic_key: the Ed25519 public key matchinguhi.tele_physical_consultation.hspa.private_key