Kulcare Docs

PHR Scan & Share — Curl Test Plan

Companion to: PHR Scan & Share API Reference Backend port: 6000 Last verified: 2026-07-23

This is a reproducible end-to-end test of the Scan & Share flow using curl.


Prerequisites

export ABHA_TOKEN="<from login>"
export USER_ID="<from kulcare_session.user.id>"
export HIP_ID="IN1810000370"
export COUNTER_ID="01900006-0000-7000-8000-000000000002"
export BASE="http://localhost:6000"

The ABHA_TOKEN is from the /phr/login/abha_address_aadhaar/verify_otp response:

{ "data": { "abha_token": "eyJ...", "kulcare_session": { "user": { "id": "uuid" } } } }

Test 1: Successful share end-to-end

Step 1 — Initiate share

curl -s -X POST "$BASE/phr/patients/share_profile" \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ABHA_TOKEN" \
  -H "REQUEST-ID: $(uuidgen)" \
  -H "TIMESTAMP: $(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
  -d "{
    \"token\": \"$ABHA_TOKEN\",
    \"hip_id\": \"$HIP_ID\",
    \"counter_id\": \"$COUNTER_ID\",
    \"abha_address\": \"simmi1508@sbx\",
    \"patient\": {
      \"healthId\": \"simmi1508@sbx\",
      \"healthIdNumber\": \"46-0315-7614-5529\",
      \"name\": \"Simmi\",
      \"gender\": \"F\",
      \"dateOfBirth\": \"1986-08-15\",
      \"mobile\": \"9815707711\"
    }
  }" | tee /tmp/share_response.json

Expected response:

{
  "success": true,
  "data": {
    "request_id":   "<uuid>",
    "status":       "pending",
    "abha_address": "simmi1508@sbx",
    "hip_id":       "IN1810000370",
    "counter_id":   "01900006-0000-7000-8000-000000000002",
    "expires_in":   30
  }
}

Extract request_id:

export REQUEST_ID=$(cat /tmp/share_response.json | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['request_id'])")
echo "REQUEST_ID=$REQUEST_ID"

Step 2 — Verify the share row was created in DB

cd /Users/simmibadhan/kulcare-platform/backend
bundle exec rails runner "
share = Phr::ProfileShare.find_by!(request_id: '$REQUEST_ID')
puts 'Status: ' + share.status
puts 'ABHA:   ' + share.abha_address
puts 'HIP:    ' + share.hip_id
puts 'Counter:' + share.counter_id
puts 'User:   ' + share.user_id.to_s
puts 'Created:' + share.created_at.to_s
" 2>&1 | grep -v "warning:\|deprecat"

Expected:

Status: pending
ABHA:   simmi1508@sbx
HIP:    IN1810000370
Counter:01900006-0000-7000-8000-000000000002
User:   <user-uuid>
Created: 2026-07-23 ...

Step 3 — Simulate ABDM's success callback

curl -s -X POST "$BASE/phr/share/on_share" \
  -H 'Content-Type: application/json' \
  -H "REQUEST-ID: $(uuidgen)" \
  -H "TIMESTAMP: $(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
  -d "{
    \"acknowledgement\": {
      \"abhaAddress\": \"simmi1508@sbx\",
      \"status\":       \"success\",
      \"context\":      \"$COUNTER_ID\",
      \"tokenNumber\":  \"3\",
      \"expiry\":       180
    },
    \"response\": { \"requestId\": \"$REQUEST_ID\" }
  }"

Expected response: {"success": true}

Step 4 — Fetch status (RESTful)

curl -s "$BASE/phr/patients/share_profile/$REQUEST_ID" | python3 -m json.tool

Expected:

{
  "success": true,
  "data": {
    "request_id":          "<uuid>",
    "status":              "success",
    "abha_address":        "simmi1508@sbx",
    "hip_id":              "IN1810000370",
    "counter_id":          "01900006-0000-7000-8000-000000000002",
    "token_number":        "3",
    "expiry":              180,
    "created_at":          "2026-07-23T...",
    "callback_received_at":"2026-07-23T..."
  }
}

Step 5 — Verify the share was updated in DB

cd /Users/simmibadhan/kulcare-platform/backend
bundle exec rails runner "
share = Phr::ProfileShare.find_by!(request_id: '$REQUEST_ID')
puts 'Status:           ' + share.status
puts 'Token Number:     ' + share.token_number.to_s
puts 'Expiry:           ' + share.expiry.to_s
puts 'Callback received:' + share.callback_received_at.to_s
" 2>&1 | grep -v "warning:\|deprecat"

Expected: status: success, token_number: 3, expiry: 180, callback_received_at populated.


Test 2: Failed share (ABDM returns error)

Step 1 — Initiate a fresh share

curl -s -X POST "$BASE/phr/patients/share_profile" \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $ABHA_TOKEN" \
  -d "{
    \"token\": \"$ABHA_TOKEN\",
    \"hip_id\": \"$HIP_ID\",
    \"counter_id\": \"$COUNTER_ID\",
    \"abha_address\": \"simmi1508@sbx\"
  }" > /tmp/share2.json
export REQUEST_ID2=$(cat /tmp/share2.json | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['request_id'])")

Step 2 — Simulate ABDM's error callback

curl -s -X POST "$BASE/phr/share/on_share" \
  -H 'Content-Type: application/json' \
  -d "{
    \"error\": { \"code\": \"ABDM-9999\", \"message\": \"Facility offline\" },
    \"response\": { \"requestId\": \"$REQUEST_ID2\" }
  }"

Step 3 — Fetch status

curl -s "$BASE/phr/patients/share_profile/$REQUEST_ID2" | python3 -m json.tool

Expected:

{
  "success": true,
  "data": {
    "request_id":     "<uuid>",
    "status":         "failed",
    "error_code":     "ABDM-9999",
    "error_message":  "Facility offline",
    "callback_received_at": "2026-07-23T..."
  }
}

Test 3: Unknown requestId (ABDM retries with bad ID)

curl -s -X POST "$BASE/phr/share/on_share" \
  -H 'Content-Type: application/json' \
  -d "{
    \"acknowledgement\": { \"status\": \"success\" },
    \"response\": { \"requestId\": \"$(uuidgen)\" }
  }"

Expected: 200 {"success": true} — we return 200 so ABDM doesn't retry.


Test 4: Missing requestId (400)

curl -s -i -X POST "$BASE/phr/share/on_share" \
  -H 'Content-Type: application/json' \
  -d '{"acknowledgement": {"status": "success"}}'

Expected: 400 Bad Request with {"success": false, "error": "requestId is required"}


Test 5: TTL sweeper (expired shares)

Step 1 — Create a stale share (created 40 minutes ago)

cd /Users/simmibadhan/kulcare-platform/backend
bundle exec rails runner "
Phr::ProfileShare.create!(
  tenant: Identity::Tenant.first,
  user_id: Identity::User.first.id,
  request_id: SecureRandom.uuid,
  hip_id: 'IN1810000370',
  counter_id: 'TEST1',
  abha_address: 'stale@sbx',
  status: 'pending',
  created_at: 40.minutes.ago,
  updated_at: 40.minutes.ago
)
puts 'Created stale share'
" 2>&1 | grep -v "warning:\|deprecat"

Step 2 — Run the sweeper

bundle exec rails runner 'Phr::ExpireStaleProfileSharesJob.perform_now' 2>&1 | grep -v "warning:\|deprecat"

Expected: logs the number of expired shares.

Step 3 — Verify the share is now expired

bundle exec rails runner "
stale = Phr::ProfileShare.where(abha_address: 'stale@sbx').last
puts 'Status:        ' + stale.status
puts 'Error code:    ' + stale.error_code.to_s
puts 'Error message: ' + stale.error_message.to_s
" 2>&1 | grep -v "warning:\|deprecat"

Expected:

Status:        expired
Error code:    CALLBACK_TIMEOUT
Error message: No ABDM on-share callback received within 1800.0 seconds

Test 6: Ably push (real-time notification)

This test requires:

  1. The FE to be subscribed to channel private:user:{user_id} with the event names abdm.share_profile.completed and abdm.share_profile.failed
  2. An Ably API key configured (Rails.application.credentials.ably_api_key)

Step 1 — Check Ably is configured

cd /Users/simmibadhan/kulcare-platform/backend
bundle exec rails runner '
puts "Ably enabled: #{Notifications::Realtime.enabled?}"
puts "Key present:  #{Rails.application.credentials.ably_api_key.present?}"
' 2>&1 | grep -v "warning:\|deprecat"

Expected: Ably enabled: true.

Step 2 — Simulate the Ably publish manually (if FE is not available)

cd /Users/simmibadhan/kulcare-platform/backend
bundle exec rails runner "
share = Phr::ProfileShare.find_by!(request_id: '$REQUEST_ID')
if share.user_id.present?
  Phr::RealtimePublisher.publish_completed(
    user_id: share.user_id,
    request_id: share.request_id
  )
  puts 'Published completed event'
else
  puts 'Share has no user_id, cannot publish'
end
" 2>&1 | grep -v "warning:\|deprecat"

The FE (or Ably debug console) should receive the event on the private:user:{user_id} channel with event name abdm.share_profile.completed.


Test 7: Missing token (400)

curl -s -i -X POST "$BASE/phr/patients/share_profile" \
  -H 'Content-Type: application/json' \
  -d "{
    \"user_id\": \"$USER_ID\",
    \"hip_id\": \"$HIP_ID\",
    \"counter_id\": \"$COUNTER_ID\"
  }"

Expected: 400 / 422 with {"success": false, "error": "Patient session token (token) is required"}


Test 8: Status for unknown requestId (404)

curl -s -i "$BASE/phr/patients/share_profile/$(uuidgen)"

Expected: 404 with {"success": false, "error": "Share not found"}


Cleanup

cd /Users/simmibadhan/kulcare-platform/backend
bundle exec rails runner '
Phr::ProfileShare.where(abha_address: "simmi1508@sbx").destroy_all
Phr::ProfileShare.where(abha_address: "stale@sbx").destroy_all
puts "Cleaned up"
' 2>&1 | grep -v "warning:\|deprecat"

On this page