M2 + M3 Comprehensive Testing Guide
Date: 2026-07-16
Branch: feature/abdm_integration_port (UNCOMMITTED — DO NOT COMMIT until you finish testing)
Scope: M2 (user-initiated linking, data flow, automatic notify flows) + M3 (HIU consent + data fetch)
0. What You've Already Tested
✅ M2 — HIP-Initiated Linking (the generate_link_token flow from m1-abha-verification.md)
- POST
/integration/abdm/abha/verifications/generate_link_token - ABDM webhook
/integration/abdm/api/v3/hip/token/on-generate-token - Ably event
abdm.link_token_generated - GET
/integration/abdm/abha/verifications/link_status abdm_api_logsandabdm_callbacksentries
✅ M1 — ABHA Verification (all 6 identifier×channel flows)
1. What This Guide Covers
This guide walks through testing every flow that has NOT been tested yet:
| Section | Flow | Tools Needed |
|---|---|---|
| §4 | M2.2 — User-Initiated Linking (full PHR app flow) | Postman + PHR Sandbox app |
| §5 | M2.3 — Encounter finalization → care context linking → auto-notify flows (U6/U7) | Postman + manual encounter finalization |
| §6 | M2.4 — Data Flow (HIP side) — consent notify + data request + FHIR bundle | Postman + HIU consent (cross-flow with M3) |
| §7 | M3 — Full HIU consent lifecycle + data fetch | Postman + PHR Sandbox app |
2. Test Environment Setup
2.1 Backend Services Required
cd backend
# Required services
redis-server # Redis
postgres -D /usr/local/var/postgres # PostgreSQL
elasticsearch # Elasticsearch (search)
# Background workers — MULTIPLE queues needed:
bundle exec sidekiq # Default queues + abdm
# Server
bundle exec rails server -p 6000 # Backend on :6000⚠️ Important: Restart
rails serverANDsidekiqafter pulling these changes (new files:care_context_notify_service.rb,sms_notify_service.rb, updated handlers).
2.2 Frontend (for E2E browser testing — optional)
cd frontend && pnpm dev
# CMS: http://localhost:5174 (or check frontend/apps/cms/package.json)
# Patient: http://localhost:5173 (for PHR app integrations)2.3 Public Webhook Tunnel
ABDM sandbox sends webhooks to YOUR backend. If your backend is on localhost:6000, ABDM can't reach it without a tunnel.
Start ngrok (or your preferred tunnel):
ngrok http 6000
# Copy the HTTPS URL, e.g. https://abc123.ngrok-free.appInject into Rails (one-time per server boot, in rails console):
cd backend
bundle exec rails console# Set callback URLs to your tunnel:
Shared::Setting.set('abdm_callback_base_url', 'https://abc123.ngrok-free.app')
# Verify:
Shared::Setting.get('abdm_callback_base_url')
# => "https://abc123.ngrok-free.app"If the setting doesn't exist yet, check how
callback_urlis read inabdm/abha/registration_service.rborhip/link_token_service.rband create the setting there. If your test environment already hardcodes a callback URL, restart with the tunnel active.
2.4 ABHA Sandbox Test Credentials
| Resource | Value |
|---|---|
| ABHA Address | johndoe@sbx (rate-limited; create your own) |
| ABHA Address | simmi2@sbx (used previously, may be rate-limited for OTP) |
| Mobile | 9876543210 |
| Aadhaar | <your-test-aadhaar> (use sandbox-recommended value, not real) |
| Sandbox base | https://dev.abdm.gov.in |
| CM-ID | sbx |
| Client-ID | SBX_001751 |
⚠️ Rate limits: ABDM sandbox rate-limits aggressively. OTP
ABDM-1027blocks for 24h after 3-5 attempts. Use care.
2.5 Pre-loaded Test Data
| Resource | ID |
|---|---|
| Tenant | 01900000-0000-7000-8000-000000000001 |
| Facility | 01900002-0000-7000-8000-000000000002 |
| Practitioner | 01900003-0000-7000-8000-000000000001 |
| Practitioner Role | 01900006-0000-7000-8000-000000000002 |
| Patient (test) | 019da93a-25bf-7060-ab97-e45dddd97947 (abha_id: simmi2@sbx) |
Why: U2 fixed JSON shape bugs in
send_on_discover,send_link_on_init,send_link_on_confirm. These were previously sendingpatient: {}(object) instead ofpatient: [](array), and missingresponse: { requestId }.
4.1 Flow Overview
PHR Sandbox App Kulcare Backend
│ │
├────[1] Patient searches for facility──►│ (no backend call;
│ │ ABDM gateway handles
│ │ patient↔HIP discovery)
│ │
│◄───[2] ABDM gateway routes discover────┤
│ webhook to HIP │
│ ├────[3] /on-discover
│◄─────────(response: { requestId })────┤ response.requestId fix
│ │
[patient selects care contexts] │
│ │
├────[4] link_init webhook──────────────►│
│ ├────[5] /on-init
│◄─────────(response + OTP SMS)─────────┤ SMS via SmsService (U5)
│ │
[patient enters OTP in PHR app] │
│ │
├────[6] link_confirm webhook───────────►│
│ ├────[7] /on-confirm
│◄─────────(patient[]: array)───────────┤ patient array fix (U2)4.2 Step-by-Step Test
Step 1-3: Discover Flow (PHR Sandbox App ↔ Kulcare)
-
Open the ABDM Sandbox PHR app (or web equivalent). URL is in ABDM developer docs — typically
https://phr.abdm.gov.inor sandbox variant. -
Log in to the PHR app with the test ABHA (
simmi2@sbxor any of yours) -
In the PHR app, search for your facility — you'll need the HIP ID (
X-HIP-ID) that was registered duringM1.1 Register facility as HIP. Find it:cd backend bundle exec rails runner ' puts Integration::AbdmFacilityRegistration.find_by(facility_id: "01900002-0000-7000-8000-000000000002")&.service_id ' -
PHR app sends discover request → ABDM gateway → your backend webhook
Step 4: Verify Inbound Discover Webhook
Check abdm_callbacks:
cd backend
bundle exec rails runner '
cb = Integration::AbdmCallback.where(callback_type: "hip.discovery.discover").order(created_at: :desc).first
if cb
puts "Callback ID: #{cb.id}"
puts "Status: #{cb.status}"
puts "Request ID: #{cb.request_id}"
puts "Tenant ID: #{cb.tenant_id}"
else
puts "NO DISCOVER CALLBACK FOUND"
end
'Expected:
- ✅ Callback exists with status
processed - ✅
request_idis populated
Step 5: Verify Outbound send_on_discover (U2 fix)
Check abdm_api_logs immediately after the discover:
cd backend
bundle exec rails runner '
log = Integration::AbdmApiLog.where(url: /on-discover/).order(created_at: :desc).first
if log
puts "URL: #{log.url}"
puts "Status: #{log.response_status}"
puts "Request payload:"
puts JSON.pretty_generate(log.request_payload)
end
'Expected:
- ✅ URL:
https://dev.abdm.gov.in/api/hiecm/user-initiated-linking/v3/patient/care-context/on-discover(NOT/api/hiecm/...with no host) - ✅ Status:
200or202 - ✅ Request payload includes:
response: { requestId: "<matching inbound requestId>" }patient: [{ ... }](ARRAY, not object)matchedBy: ['ABHA_ADDRESS']
Step 6-7: Link_Init Flow
-
In PHR app, select care contexts to link → PHR app sends link_init webhook
-
Check
abdm_api_logsfor/on-init:cd backend bundle exec rails runner ' log = Integration::AbdmApiLog.where(url: /on-init/).order(created_at: :desc).first puts "Status: #{log.response_status}" puts "Body: #{log.request_payload.except("response").to_json}" 'Expected:
response.requestIdpresent. -
Check SMS delivery (U5):
cd backend bundle exec rails runner ' logs = Shared::NotificationLog.where(channel: "sms").order(created_at: :desc).limit(3) logs.each { |l| puts "#{l.recipient} | #{l.content[0..60]}... | #{l.status}" } 'Expected: SMS with the OTP code sent to the patient's mobile (status
sentorpending). -
Check
AbdmDiscoveryRequest:cd backend bundle exec rails runner ' d = Integration::AbdmDiscoveryRequest.order(created_at: :desc).first puts "Status: #{d.status}" puts "Verification code: #{d.verification_code}" puts "Patient: #{d.patient&.name}" 'Expected:
status: 'link_initiated',verification_code: 'XXXXXX'. -
PHR app shows OTP screen → patient enters OTP → sends
link_confirmwebhook
Step 8-9: Link_Confirm Flow
-
PHR app sends link_confirm after patient enters OTP
-
Check
abdm_api_logsfor/on-confirm:cd backend bundle exec rails runner ' log = Integration::AbdmApiLog.where(url: /on-confirm/).order(created_at: :desc).first puts "Status: #{log.response_status}" puts "Body: #{log.request_payload.to_json}" 'Expected:
- ✅ URL correct
- ✅
response: { requestId: ... } - ✅
patient: [{ ... }](ARRAY)
-
Verify linked care contexts:
cd backend bundle exec rails runner ' ctx = Integration::AbdmCareContext.where(discovery_request_id: Integration::AbdmDiscoveryRequest.order(:created_at).last.id) puts "Count: #{ctx.count}" ctx.each { |c| puts "#{c.care_context_reference} | #{c.sync_status}" } 'Expected: care contexts linked with
sync_status: 'linked'.
4.3 Pass Criteria for M2.2
- ✅ Outbound calls have correct URLs (full
https://dev.abdm.gov.in/...) - ✅ Outbound calls include
response: { requestId } - ✅ Outbound calls have
patient: []arrays (where applicable) - ✅ SMS delivered to patient (visible in
notification_logs) - ✅
AbdmDiscoveryRequesttransitions throughpending → matched → link_initiated → link_confirmed - ✅ Care contexts created with
sync_status: 'linked'
5. M2.3 — Care Context Linking + Auto-Notify (U4, U6, U7)
Why: U4 wires FHIR bundles; U6 auto-fires context/notify; U7 auto-fires sms/notify. Verify all three auto-fires work after encounter finalization.
5.1 Pre-requisites
- A patient with linked ABHA via M2.1 (or earlier session)
- An encounter that can be "finalized" (transitioned to
finished) - Active HIP registration
5.2 Step-by-Step Test
Step 1: Verify Patient Has Valid Link Token
cd backend
bundle exec rails runner '
p = Clinical::Patient.find("019da93a-25bf-7060-ab97-e45dddd97947")
lr = Integration::AbdmLinkRequest.for_patient(p.id).order(created_at: :desc).first
puts "Link request ID: #{lr&.id}"
puts "Token present: #{lr&.link_token.present?}"
puts "Token valid: #{lr&.token_valid?}"
puts "Status: #{lr&.status}"
puts "ABHA: #{lr&.abha_address}"
'If token is expired/missing: call POST /integration/abdm/abha/verifications/generate_link_token (the flow you already tested).
Step 2: Create or Find an Encounter
If you have an in-progress encounter with prescriptions/observations already:
cd backend
bundle exec rails runner '
e = Clinical::Encounter.where(patient_id: "019da93a-25bf-7060-ab97-e45dddd97947").order(created_at: :desc).first
puts "Encounter: #{e.id}"
puts "Status: #{e.status}"
puts "Medications: #{e.medication_requests.count}"
puts "Observations: #{e.observations.count}"
puts "Created: #{e.created_at}"
'If you don't have one, create via CMS UI: Add Appointment → Check-in → Encounter.
Step 3: Add Medications + Observations (if not already present)
Skip if encounter already has them.
Step 4: Trigger Care Context Linking (encounter finalization)
If encounter is still in_progress, transition to finished:
cd backend
bundle exec rails runner '
e = Clinical::Encounter.where(patient_id: "019da93a-25bf-7060-ab97-e45dddd97947").order(created_at: :desc).first
# Simulate the transition
e.update!(status: "finished")
Integration::Abdm::EnqueueCareContextLinkJob.perform_later(e.id)
'Or via Postman if there's an encounter finalization endpoint (check clinical/encounters_controller.rb).
Step 5: Wait + Verify
Wait ~30 seconds for the job chain:
EnqueueCareContextLinkJob → CareContextLinkingService#call → ABDM API → HipLinkHandler#on_add_contexts → auto-fires (SmsNotifyService) → ABDM webhook → handler
cd backend
bundle exec rails runner '
# Latest link request
lr = Integration::AbdmLinkRequest.order(created_at: :desc).first
puts "Link request status: #{lr.status}"
# Care contexts created
ctx = Integration::AbdmCareContext.where(link_request: lr)
puts "Care contexts: #{ctx.count}"
# U4: FHIR bundles populated
puts "FHIR bundles populated: #{ctx.where.not(fhir_bundle: nil).count} of #{ctx.count}"
ctx.each do |c|
bundle_size = c.fhir_bundle.is_a?(Hash) ? c.fhir_bundle["entry"]&.size : "nil"
puts " #{c.care_context_reference} | #{c.resource_type} | fhir entries: #{bundle_size} | sync_status: #{c.sync_status}"
end
'Expected:
- ✅
link_request.status: 'completed' - ✅ Care contexts exist (1 OPConsultation + N prescriptions + M observations)
- ✅ U4 fix:
fhir_bundleis NOT null for OPConsultation + Prescription contexts (DiagnosticReport still nil — builder doesn't exist yet) - ✅
sync_status: 'linked'
Step 6: Verify U6 Auto-Fire (Care Context Notify)
Check outbound abdm_api_logs:
cd backend
bundle exec rails runner '
logs = Integration::AbdmApiLog.where(url: /link\/context\/notify/).order(created_at: :desc).limit(5)
puts "Count: #{logs.count}"
logs.each do |l|
puts "#{l.http_method} #{l.response_status} #{l.url}"
puts " payload keys: #{l.request_payload.keys}"
puts " patient count: #{l.request_payload["patient"]&.size}"
puts " hiTypes: #{l.request_payload["hiTypes"]}"
end
'Expected: 1+ outbound calls to /link/context/notify, status 200/202.
Check U6 inbound webhook (hip.context_notify):
cd backend
bundle exec rails runner '
cb = Integration::AbdmCallback.where(callback_type: "hip.context_notify").order(created_at: :desc).first
if cb
puts "Callback status: #{cb.status}"
puts "Request ID: #{cb.request_id}"
else
puts "NO CONTEXT_NOTIFY CALLBACK (ABDM may not have one queued yet)"
end
'Check AbdmCareContext.metadata:
cd backend
bundle exec rails runner '
c = Integration::AbdmCareContext.where.not(metadata: nil).order(created_at: :desc).first
puts "Metadata keys: #{c.metadata.keys}"
puts "last_notify_at: #{c.metadata["last_notify_at"]}"
puts "last_notify_request_id: #{c.metadata["last_notify_request_id"]}"
'Expected: last_notify_at + last_notify_request_id populated by the inbound webhook.
Step 7: Verify U7 Auto-Fire (SMS Notify)
Check outbound abdm_api_logs:
cd backend
bundle exec rails runner '
logs = Integration::AbdmApiLog.where(url: /sms\/notify2/).order(created_at: :desc).limit(5)
puts "Count: #{logs.count}"
logs.each do |l|
puts "#{l.response_status} #{l.url}"
puts " payload: #{l.request_payload.to_json}"
end
'Expected: 1+ outbound calls to /sms/notify2, status 200/202.
Check AbdmLinkRequest.metadata:
cd backend
bundle exec rails runner '
lr = Integration::AbdmLinkRequest.order(created_at: :desc).first
puts "last_sms_notify_at: #{lr.metadata&.dig("last_sms_notify_at")}"
puts "last_sms_notify_request_id: #{lr.metadata&.dig("last_sms_notify_request_id")}"
'Check U7 inbound webhook (hip.sms_notify):
cd backend
bundle exec rails runner '
cb = Integration::AbdmCallback.where(callback_type: "hip.sms_notify").order(created_at: :desc).first
puts cb ? "Callback: #{cb.status}" : "NO SMS_NOTIFY CALLBACK"
'5.3 Pass Criteria for M2.3
- ✅ Encounter finalization triggers care context linking (already worked, but verify)
- ✅ U4:
fhir_bundlepopulated on OPConsultation + Prescription care contexts (not nil) - ✅ U6: Auto-fired outbound call to
/link/context/notifyrecorded inabdm_api_logs - ✅ U6: Inbound
hip.context_notifywebhook received and processed - ✅ U7: Auto-fired outbound call to
/sms/notify2recorded inabdm_api_logs - ✅ U7: Inbound
hip.sms_notifywebhook received and processed
6. M2.4 + Cross-Test with M3 — Data Flow (HIP side)
Why: U1 fixed
DataTransferService#gateway_url. U3 added HIP consent ACK. Verify the full data flow works end-to-end.
This section overlaps with §7 (M3) — the M3 "fetch data" step is what triggers the M2 "push data" step. Test them together.
6.1 Pre-requisites
- Patient has been linked via M2 (any flow)
- HIU is registered (M1.2 in Postman collection — needed before §7 M3)
6.2 Test Steps
Step 1: Get Your HIU ID
cd backend
bundle exec rails runner '
reg = Integration::AbdmFacilityRegistration.find_by(facility_id: "01900002-0000-7000-8000-000000000002", registration_type: "HIU")
puts reg&.service_id
'Step 2: Create a Consent Request from HIU
Use Postman M3.1 Create HIU consent request with body:
{
"patient_id": "019da93a-25bf-7060-ab97-e45dddd97947",
"requester_id": "<HIU_ID_FROM_STEP_1>",
"requester_type": "HIU",
"purpose": "care_management",
"hi_types": ["Prescription", "DiagnosticReport"],
"date_range_from": "2024-01-01",
"date_range_to": "2026-12-31",
"data_erase_at": "2026-12-31T23:59:59Z"
}Capture consent_id (auto-saved by Postman test script).
Step 3: Patient Approves in PHR App
- Patient logs into PHR Sandbox app
- Notification: "Facility X requests your records"
- Patient clicks Approve
Step 4: Verify Webhook Inbound
cd backend
bundle exec rails runner '
cb = Integration::AbdmCallback.where(callback_type: "hip.consent_notify").order(created_at: :desc).first
puts cb ? "Status: #{cb.status} | Request ID: #{cb.request_id}" : "NO CONSENT_NOTIFY CALLBACK"
consent = Integration::AbdmConsent.order(created_at: :desc).first
puts "Consent status: #{consent.status}"
puts "Consent ID (ABDM): #{consent.consent_id}"
puts "Consent expiry: #{consent.consent_expiry}"
'Expected:
- ✅ Callback
status: 'processed' - ✅ Consent record exists with
status: 'granted'
Step 5: Verify U3 Auto-Fire (HIP Consent ACK)
cd backend
bundle exec rails runner '
log = Integration::AbdmApiLog.where(url: /hip\/on-notify/).order(created_at: :desc).first
puts "URL: #{log.url}"
puts "Status: #{log.response_status}"
puts "Payload: #{log.request_payload.to_json}"
'Expected:
- ✅ URL:
https://dev.abdm.gov.in/api/hiecm/consent/v3/request/hip/on-notify - ✅ Payload includes
acknowledgement: { status: "OK" }+response: { requestId } - ✅ Status:
200/202
Step 6: Continue with M3 §7 — fetch data
(Now in §7 below — proceed to M3.5+)
7. M3 — HIU Consent Management Full Lifecycle
See
/Users/simmibadhan/Projects/Kulcare/kulcare-platform/docs/abdm/api-reference/m3-consent-management.mdfor full API reference. This section gives you the testing sequence.
7.1 Pre-requisites (Re-Checked)
- ✅ Patient has linked ABHA (from M1/M2)
- ✅ HIU registered (Postman M1.2)
- ✅ CM-ID and gateway credentials set
- ✅ Backend
rails serverandsidekiqrunning - ✅
abdm_callbacksqueue active (verify withSidekiq::Queue.new("abdm_callbacks").size)
7.2 Test Sequence
Step 1: M3.1 Create HIU Consent Request (Postman)
Use Postman. Body as in §6.2 Step 2.
Verify:
cd backend
bundle exec rails runner '
c = Integration::AbdmConsent.order(created_at: :desc).first
puts "Consent_request_id (ABDM): #{c.consent_request_id}"
puts "Status: #{c.status}"
puts "Expires: #{c.consent_expiry}"
'Expected:
- ✅
consent_request_idpopulated (U2 fix — was nil before) - ✅ Status:
requestedor similar
Step 2: M3.2 Approve Consent (Patient in PHR App)
- Open PHR Sandbox app on phone/web
- Log in as patient
- Notifications → "Consent Request from Facility X"
- Review what data is being requested
- Approve
Verify inbound webhook:
cd backend
bundle exec rails runner '
cb = Integration::AbdmCallback.where(callback_type: "hiu.consent_notify").order(created_at: :desc).first
puts cb ? "Callback: #{cb.status}" : "NO HIU CONSENT_NOTIFY CALLBACK"
c = Integration::AbdmConsent.order(created_at: :desc).first
puts "Local status: #{c.status}"
puts "consent_request_id: #{c.consent_request_id}"
'Expected:
- ✅ Callback
status: 'processed' - ✅ Local consent status updates (e.g.,
granted) - ✅
consent_request_idSTILL present (U2 fix was about persistence)
Step 3: Verify HIU on-notify ACK (U3 M3 fix)
cd backend
bundle exec rails runner '
log = Integration::AbdmApiLog.where(url: /hiu\/on-notify/).order(created_at: :desc).first
puts "URL: #{log.url}"
puts "Status: #{log.response_status}"
puts "Payload: #{log.request_payload.to_json}"
'Expected: ACK outbound to /consent/v3/request/hiu/on-notify.
Step 4: Verify Ably Event abdm.consent_status_changed
cd backend
bundle exec rails runner '
c = Integration::AbdmConsent.order(created_at: :desc).first
puts "requested_by_id: #{c.requested_by_id}"
puts "requested_by_type: #{c.requested_by_type}"
'Then subscribe to the Ably channel private:user:<requested_by_id> from your
FE/dev tools and verify a abdm.consent_status_changed event was published.
If you don't have an Ably client: skip this verification — the publish is fire-and-forget with logging.
Step 5: M3.5-M3.6 List + Show Consent (Postman)
- GET
/integration/abdm/consents?patient_id=...— verify consent appears - GET
/integration/abdm/consents/:id— verify detailed view
Step 6: M3.7 List Consent Artefacts (Postman)
GET /integration/abdm/cm/artefacts?consent_id=...
Expected: Empty or [] initially (no data fetched yet).
Step 7: Trigger Data Fetch from HIU
This is the M3.5-equivalent on the data side. In a real HIU, you'd call ABDM's data-flow endpoint to fetch the consented records. For testing, you can either:
Option A (simulate HIU request):
cd backend
bundle exec rails runner '
# Simulate an HIU data request via the same backend path
consent = Integration::AbdmConsent.find_by(status: "granted")
# The HIU would normally call:
# POST /api/hiecm/data-flow/v3/health-information/request
# which causes ABDM to fire /on_request webhook at the HIP
puts "Manual trigger: simulate by calling DataFetchService"
'Option B: Use Postman M3 endpoints if there are "request data" ones.
For sandbox testing without an actual HIU client app, you may need to:
- Construct an ABDM data request manually (via Postman to ABDM with HIU creds)
- Wait for ABDM to send the
hip_data_requestwebhook to your backend - Verify your backend:
- Creates
AbdmDataTransferrecord - Sends HIP on-request ACK (U1 fix)
- Acknowledges data
- Pushes encrypted FHIR bundles to HIU
- Sends data-flow notify
- Creates
cd backend
bundle exec rails runner '
dt = Integration::AbdmDataTransfer.order(created_at: :desc).first
if dt
puts "Data transfer: #{dt.status}"
puts "Transaction: #{dt.transaction_id}"
puts "Consent: #{dt.abdm_consent_id}"
else
puts "NO DATA TRANSFER (ABDM data flow not triggered)"
end
'Step 8: M3.8 Show Artefact (Postman)
After data is fetched, GET /integration/abdm/cm/artefacts/:id should return
the artefact with fhir_records populated.
7.3 Pass Criteria for M3
- ✅ Consent request creates record with
consent_request_id(U2) - ✅ Patient approval triggers inbound
hiu.consent_notifywebhook - ✅ HIP consent ACK + HIU consent ACK sent (U3 M2 + U3 M3)
- ✅ Consent status updates to
granted - ✅ Ably event
abdm.consent_status_changedpublished - ✅ M3.9 GET status endpoint returns latest status from ABDM
- ✅ Optional: Data fetch triggers the full data flow
8. Quick Debugging Tips
8.1 Most Common Issues
| Symptom | Likely Cause | Fix |
|---|---|---|
abdm_api_logs shows URL without host | U1 regression — .dig(:abdm, ...) | Check credential access pattern |
| ABDM rejects patient array as schema mismatch | U2 regression — patient: {} (object) | Wrap in [] |
| Webhook returns 200 but ABDM times out | Missing response: { requestId } | Add envelope |
| SMS not delivered | SmsService failed silently | Check notification_logs for failure reason |
| FHIR bundle nil in data transfer | U4 regression | Check FHIR builder associations match encounter shape |
404 Not Found on /api/v3/.../on-notify | Route missing | Verify routes file has the new entries |
| No Ably event fired | Ably disabled in test env | Check Notifications::Realtime.enabled? |
8.2 Useful Queries
# All recent ABDM logs
cd backend
bundle exec rails runner '
Integration::AbdmApiLog.order(created_at: :desc).limit(20).each do |l|
puts "#{l.created_at} | #{l.http_method} #{l.response_status} | #{l.url[0..80]}"
end
'
# All recent ABDM callbacks (webhooks)
cd backend
bundle exec rails runner '
Integration::AbdmCallback.order(created_at: :desc).limit(20).each do |c|
puts "#{c.created_at} | #{c.callback_type} | #{c.status} | retry: #{c.retry_count}"
end
'
# Failed webhooks (retry > 0)
cd backend
bundle exec rails runner '
Integration::AbdmCallback.where("retry_count > 0 OR status = ?", "failed").order(created_at: :desc).limit(10).each do |c|
puts "#{c.callback_type} | #{c.status} | #{c.error_code} | #{c.error_message}"
end
'
# Sidekiq queue health
cd backend
bundle exec rails runner '
puts "abdm queue: #{Sidekiq::Queue.new("abdm").size}"
puts "abdm_callbacks queue: #{Sidekiq::Queue.new("abdm_callbacks").size}"
puts "abdm failed: #{Sidekiq::DeadSet.new.size}"
'8.3 When Something Goes Wrong
- Capture exactly what failed: postman response body + server log line
- Check
abdm_callbacksqueue:Sidekiq::Queue.new("abdm_callbacks").size— if growing, sidekiq may be down - Check Sidekiq:
tail -f log/sidekiq.log - Re-fire a specific callback:
cd backend bundle exec rails runner ' cb = Integration::AbdmCallback.find(<id>) cb.update!(status: "received") # Reset Integration::Abdm::CallbackProcessor.new(cb).process! '
9. Test Result Tracker
Use this checklist as you go:
M2.2 — User-Initiated Linking (PHR app)
- Discover webhook arrives
-
/on-discoveroutbound hasresponse.requestId+patient: [] - Link_init webhook arrives
-
/on-initoutbound hasresponse.requestId - SMS delivered to patient
- Link_confirm webhook arrives
-
/on-confirmoutbound hasresponse.requestId+patient: [] - Care contexts linked
M2.3 — Care Context + Auto-Notify
- Care contexts created with
fhir_bundlepopulated (U4) - Link request transitions to
completed -
/link/context/notifyoutbound fires (U6) -
/api/v3/links/context/on-notifyinbound received (U6) -
/sms/notify2outbound fires (U7) -
/api/v3/patients/sms/on-notifyinbound received (U7)
M2.4 / M3 — Data Flow
- Consent request creates with
consent_request_idpopulated (U2-M3) - Patient approves in PHR → inbound
hiu.consent_notify - HIU on-notify ACK outbound (U3-M3)
- Consent status updates to
granted - Ably event
abdm.consent_status_changedfires - (Optional) HIU data request triggers
hip_data_requestwebhook - (Optional)
DataTransferServiceencrypts + pushes + notifies - (Optional) FHIR bundles in transfer NOT empty (U4)
10. When You're Done
Once all test results are filled in, let me know:
- All green? → I'll help commit the M2 + M3 changes
- Some red? → I'll help debug + fix
- Questions about flow? → Ask before testing that section
The original plan (docs/plans/2026-07-10-003-feat-abdm-m2-gap-fixes-plan.md)
stays in the working tree for reference.