Referral Tier Integration Guide
📄 This guide is also available as a PDF download.
Version: 0.1 (Draft)
Date: March 2, 2026
Base URL: https://api.stealth.health (production) | https://sandbox.stealth.health (development)
Status: Proposal / Scoping
See also: Clinical Partner API Integration Guide — a deeper integration where patient data, intake responses, appointment details, and transaction history are shared with the partner under a full BAA.
1. Overview​
Stealth Health offers a white-label telemedicine integration that allows partner platforms to embed our enrollment, clinical review, and prescription fulfillment pipeline into their own product. The integration works as follows:
- Partner directs their customer to a co-branded Stealth Health enrollment page.
- Patient completes a medical intake questionnaire and (optionally) pays.
- Stealth Health doctors review the intake and approve or deny a prescription.
- Stealth Health pharmacy partners fulfill and ship approved prescriptions.
- Partner receives real-time status updates via webhooks and can query the API for current state.
The partner never handles, stores, or receives protected health information (PHI). All clinical data stays within the Stealth Health platform. Partners interact exclusively with de-identified references and lifecycle status events.
2. HIPAA Compliance & PHI Boundaries​
What Partners CAN Access​
| Data Point | Example |
|---|---|
| Partner-scoped reference ID | ref_abc123xyz |
| Enrollment status | enrolled, pending_review, approved, denied |
| Fulfillment status | awaiting_shipment, shipped, delivered |
| Timestamp of each status change | 2026-03-02T14:30:00Z |
| Product category enrolled for | trt-cream, peptides |
| Payment status | payment_due, payment_complete |
| Tracking carrier (no tracking #) | USPS, UPS, Canada Post |
What Partners CANNOT Access​
| Data Point | Reason |
|---|---|
| Patient name, DOB, gender | PHI — identity |
| Address, phone, email | PHI — contact info |
| Medical conditions, symptoms, allergies | PHI — clinical |
| Prescription details (medication, dosage, quantity) | PHI — treatment |
| Doctor notes, intake responses | PHI — clinical |
| Lab results, ID verification images | PHI — clinical / PII |
| Tracking numbers, shipping labels | Could be used to correlate PHI |
Note: If a partner needs to display fulfillment tracking to their end user, they should direct the user to their Stealth Health patient portal, or we can discuss a tokenized tracking link approach (see Section 6.6).
Business Associate Agreement (BAA)​
A signed BAA between Stealth Health and the partner is required before API credentials are issued. The BAA defines each party's obligations under HIPAA and limits the partner's role to a referral source — not a covered entity or business associate handling PHI.
3. Authentication & Security​
3.1 API Keys​
Each partner receives a pair of API credentials:
| Credential | Purpose |
|---|---|
X-Partner-ID | Identifies the partner (public, safe to log) |
X-Api-Key | Authenticates the request (secret, never log or expose client-side) |
Both must be sent as HTTP headers on every request:
GET /partner/referrals HTTP/1.1
Host: api.stealth.health (or sandbox.stealth.health)
X-Partner-ID: ptr_acme_health
X-Api-Key: sk_live_7f3a...redacted
Content-Type: application/json
3.2 Key Rotation​
Partners can request key rotation at any time. When a new key is issued, the old key remains valid for 72 hours to allow migration. Both keys are accepted during the overlap window.
3.3 Webhook Signature Verification​
All webhook payloads include an X-Stealth-Signature header containing an HMAC-SHA256 signature of the request body, signed with the partner's webhook secret (provided at onboarding, separate from the API key).
X-Stealth-Signature: sha256=a1b2c3d4e5f6...
Partners must verify this signature before processing any webhook payload. See Section 7 for implementation details.
3.4 Transport Security​
- All API traffic must use TLS 1.2+.
- IP allowlisting is available on request.
- All requests are logged with partner ID, endpoint, timestamp, and response code (no PHI is logged).
4. Partner Onboarding​
Step 1: BAA Execution​
Sign the Business Associate Agreement.
Step 2: Configuration​
Stealth Health provisions the following for each partner:
| Item | Description |
|---|---|
partner_id | Unique partner identifier (e.g. ptr_acme_health) |
| API key pair | Live and sandbox credentials |
| Webhook secret | For verifying webhook signatures |
| Enrollment slug(s) | Custom enrollment page URLs (e.g. enroll.stealth.health/acme/trt-cream) |
| Branding config | Logo, colors, and copy for co-branded enrollment pages |
| Webhook URL | Partner's HTTPS endpoint for receiving events |
Step 3: Enrollment Page Setup​
Stealth Health creates partner-specific enrollment pages based on the product categories the partner wants to offer. These pages:
- Use the partner's branding (logo, color scheme, custom copy).
- Include the appropriate medical intake questionnaire.
- Are accessible via partner-specific URLs or can be embedded via iframe.
- Automatically tag all submissions with the partner's
partner_id.
Step 4: Sandbox Testing​
Partners test the full lifecycle in sandbox before going live.
Step 5: Go Live​
Switch to production credentials and enrollment URLs.
5. Integration Flow​
5.1 Sequence Diagram​

5.2 Step-by-Step​
Step 1 — Create Referral
Partner calls POST /partner/referrals with a partner_reference (the partner's own customer ID) and the desired product_category. Stealth Health returns a referral_id and a unique enrollment URL.
Step 2 — Customer Enrollment Partner redirects or links their customer to the enrollment URL. The customer sees a co-branded page and completes the medical intake questionnaire.
Step 3 — Payment If payment is required for the product category, the customer pays during enrollment. Some categories are consult-only (payment after approval).
Step 4 — Clinical Review A licensed Stealth Health physician reviews the intake and either approves a prescription or denies the request (with a reason communicated to the patient, not the partner).
Step 5 — Fulfillment Approved prescriptions are sent to a licensed pharmacy partner for compounding and shipping. Partners receive status updates at each stage.
6. API Reference​
All endpoints are prefixed with /partner.
6.1 POST /partner/referrals​
Create a new patient referral. Call this before sending a customer to enrollment.
Request:
{
"partner_reference": "cust_12345",
"product_category": "trt-cream",
"metadata": {
"campaign": "spring-2026",
"source": "website"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
partner_reference | string | Yes | Partner's own identifier for this customer. Must be unique per partner. Max 128 chars. |
product_category | string | Yes | One of the available categories (see 6.5). |
metadata | object | No | Arbitrary key-value pairs for partner's own tracking (max 20 keys, 500 chars per value). Not used by Stealth Health. |
Response (201 Created):
{
"referral_id": "ref_abc123xyz",
"partner_reference": "cust_12345",
"product_category": "trt-cream",
"enrollment_url": "https://enroll.stealth.health/acme/trt-cream?ref=ref_abc123xyz",
"status": "created",
"created_at": "2026-03-02T14:00:00Z",
"expires_at": "2026-03-09T14:00:00Z"
}
Note: Enrollment URLs expire after 7 days. A new referral must be created if the link expires.
6.2 GET /partner/referrals/:referral_id​
Retrieve the current status of a referral.
Response (200 OK):
{
"referral_id": "ref_abc123xyz",
"partner_reference": "cust_12345",
"product_category": "trt-cream",
"status": "approved",
"status_history": [
{ "status": "created", "at": "2026-03-02T14:00:00Z" },
{ "status": "enrolled", "at": "2026-03-02T14:15:00Z" },
{ "status": "pending_review", "at": "2026-03-02T14:15:00Z" },
{ "status": "approved", "at": "2026-03-03T09:30:00Z" }
],
"fulfillment": {
"status": "shipped",
"carrier": "USPS",
"estimated_delivery": "2026-03-07",
"updated_at": "2026-03-04T11:00:00Z"
},
"payment": {
"status": "paid",
"amount_cents": 14900,
"currency": "USD",
"paid_at": "2026-03-02T14:15:00Z"
},
"metadata": {
"campaign": "spring-2026",
"source": "website"
},
"created_at": "2026-03-02T14:00:00Z"
}
6.3 GET /partner/referrals​
List all referrals for the authenticated partner.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
status | string | (all) | Filter by status: created, enrolled, pending_review, approved, denied, expired |
fulfillment_status | string | (all) | Filter by fulfillment: awaiting_shipment, shipped, in_transit, delivered |
partner_reference | string | — | Look up by partner's own reference |
product_category | string | (all) | Filter by category |
created_after | ISO 8601 | — | Only referrals created after this timestamp |
created_before | ISO 8601 | — | Only referrals created before this timestamp |
limit | integer | 50 | Max results per page (1–200) |
cursor | string | — | Pagination cursor from previous response |
Response (200 OK):
{
"referrals": [
{ "referral_id": "ref_abc123xyz", "partner_reference": "cust_12345", "product_category": "trt-cream", "status": "approved", "..." : "..." },
{ "referral_id": "ref_def456uvw", "partner_reference": "cust_67890", "product_category": "peptides", "status": "enrolled", "..." : "..." }
],
"pagination": {
"has_more": true,
"next_cursor": "eyJjcmVhdGVkX2F0Ijo..."
}
}
6.4 GET /partner/referrals/summary​
Aggregate counts for dashboard / reporting.
Query Parameters:
| Param | Type | Description |
|---|---|---|
period | string | day, week, month, all_time (default: month) |
product_category | string | Filter by category |
Response (200 OK):
{
"period": "month",
"start": "2026-02-01T00:00:00Z",
"end": "2026-02-28T23:59:59Z",
"totals": {
"created": 150,
"enrolled": 120,
"pending_review": 15,
"approved": 90,
"denied": 12,
"expired": 3,
"shipped": 75,
"delivered": 60
},
"by_category": {
"trt-cream": { "created": 80, "approved": 50, "denied": 5 },
"peptides": { "created": 40, "approved": 30, "denied": 4 }
},
"revenue": {
"total_cents": 756000,
"currency": "USD"
}
}
6.5 GET /partner/products​
List product categories available to this partner.
Response (200 OK):
{
"products": [
{
"category": "trt-cream",
"display_name": "Testosterone Replacement Therapy (Cream)",
"description": "Compounded testosterone cream for hormone optimization",
"jurisdictions": ["US"],
"enrollment_url_template": "https://enroll.stealth.health/acme/trt-cream?ref={referral_id}",
"requires_payment_at_enrollment": true,
"price_range": {
"min_cents": 9900,
"max_cents": 19900,
"currency": "USD"
}
},
{
"category": "peptides",
"display_name": "BPC-157 Peptide Therapy",
"description": "BPC-157 peptide for tissue repair and recovery support",
"jurisdictions": ["US"],
"enrollment_url_template": "https://enroll.stealth.health/acme/peptides?ref={referral_id}",
"requires_payment_at_enrollment": true,
"price_range": {
"min_cents": 14900,
"max_cents": 24900,
"currency": "USD"
}
}
]
}
6.6 GET /partner/referrals/:referral_id/tracking-link​
Generate a tokenized, time-limited tracking link that the partner can display to their end user. The link opens the Stealth Health patient portal's shipment tracking view without exposing the tracking number to the partner.
Response (200 OK):
{
"referral_id": "ref_abc123xyz",
"tracking_url": "https://portal.stealth.health/track/tkn_9f8e7d6c5b4a...",
"expires_at": "2026-03-05T14:00:00Z"
}
Returns
404if no shipment exists for the referral yet.
6.7 POST /partner/referrals/:referral_id/cancel​
Request cancellation of a referral. Only possible if the referral has not yet reached approved status.
Request:
{
"reason": "Customer requested cancellation"
}
Response (200 OK):
{
"referral_id": "ref_abc123xyz",
"status": "cancelled",
"cancelled_at": "2026-03-02T16:00:00Z"
}
Returns
409 Conflictif the referral is already approved or further in the lifecycle.
7. Webhook Events​
7.1 Overview​
Stealth Health sends HTTPS POST requests to the partner's registered webhook URL whenever a referral's status changes. Events are delivered at least once — partners must handle idempotency.
7.2 Payload Format​
{
"event_id": "evt_1a2b3c4d",
"event_type": "referral.approved",
"referral_id": "ref_abc123xyz",
"partner_reference": "cust_12345",
"data": {
"status": "approved",
"product_category": "trt-cream",
"occurred_at": "2026-03-03T09:30:00Z"
},
"metadata": {
"campaign": "spring-2026"
},
"created_at": "2026-03-03T09:30:01Z"
}
7.3 Event Types​
| Event | Trigger | data includes |
|---|---|---|
referral.enrolled | Patient completes intake form | product_category |
referral.pending_review | Intake submitted for doctor review | — |
referral.approved | Doctor approves prescription | — |
referral.denied | Doctor denies prescription | denial_category (e.g. medical_contraindication, incomplete_information, not_a_candidate) |
referral.payment_due | Payment required from patient | amount_cents, currency |
referral.payment_complete | Patient payment received | amount_cents, currency |
referral.awaiting_shipment | Prescription sent to pharmacy | — |
referral.shipped | Order shipped by pharmacy | carrier, estimated_delivery |
referral.in_transit | Shipment in transit (carrier scan) | carrier |
referral.out_for_delivery | Shipment out for delivery | carrier |
referral.delivered | Shipment delivered | carrier, delivered_at |
referral.delivery_exception | Delivery issue (failed attempt, etc.) | carrier, exception_type |
referral.cancelled | Referral cancelled | cancelled_by (partner or patient) |
referral.expired | Enrollment URL expired without completion | — |
7.4 Signature Verification​
import hmac
import hashlib
def verify_signature(payload_body: bytes, signature_header: str, webhook_secret: str) -> bool:
expected = "sha256=" + hmac.new(
webhook_secret.encode(),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
7.5 Retry Policy​
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 12 hours |
After 6 failed attempts (non-2xx response or timeout), the event is placed in a dead letter queue. Partners can retrieve missed events via GET /partner/events (see 6.8).
Partners must respond with a 2xx status code within 10 seconds to acknowledge receipt.
8. Data Models​
8.1 Referral Object​
{
"referral_id": "string — Stealth Health's unique identifier",
"partner_reference": "string — Partner's customer identifier",
"product_category": "string — e.g. trt-cream, peptides",
"status": "string — See Appendix: Status Lifecycle",
"status_history": [
{ "status": "string", "at": "ISO 8601 timestamp" }
],
"fulfillment": {
"status": "string | null",
"carrier": "string | null",
"estimated_delivery": "string (YYYY-MM-DD) | null",
"updated_at": "ISO 8601 timestamp | null"
},
"payment": {
"status": "string — due | paid | refunded | not_applicable",
"amount_cents": "integer | null",
"currency": "string — USD | CAD",
"paid_at": "ISO 8601 timestamp | null"
},
"metadata": "object — Partner-provided key-value pairs",
"created_at": "ISO 8601 timestamp",
"expires_at": "ISO 8601 timestamp — Enrollment link expiry"
}
8.2 Event Object​
{
"event_id": "string — Unique event identifier (for idempotency)",
"event_type": "string — e.g. referral.approved",
"referral_id": "string",
"partner_reference": "string",
"data": "object — Event-specific payload (see Section 7.3)",
"metadata": "object — Mirror of referral metadata",
"created_at": "ISO 8601 timestamp"
}
9. Error Handling​
9.1 HTTP Status Codes​
| Code | Meaning |
|---|---|
200 | Success |
201 | Created |
400 | Bad request — invalid parameters |
401 | Unauthorized — invalid or missing API key |
403 | Forbidden — valid key but insufficient permissions |
404 | Not found — referral doesn't exist or doesn't belong to this partner |
409 | Conflict — action not allowed in current state |
422 | Unprocessable — valid syntax but business rule violation |
429 | Rate limited |
500 | Internal server error |
9.2 Error Response Format​
{
"error": {
"code": "REFERRAL_NOT_CANCELLABLE",
"message": "Referral ref_abc123xyz cannot be cancelled because it has already been approved.",
"details": {
"referral_id": "ref_abc123xyz",
"current_status": "approved"
}
}
}
9.3 Common Error Codes​
| Code | Description |
|---|---|
INVALID_PARTNER_REFERENCE | partner_reference already used or invalid format |
INVALID_PRODUCT_CATEGORY | Category not available to this partner |
REFERRAL_NOT_FOUND | Referral doesn't exist or unauthorized |
REFERRAL_NOT_CANCELLABLE | Referral past the cancellable stage |
REFERRAL_EXPIRED | Enrollment link has expired |
RATE_LIMITED | Too many requests |
10. Rate Limits​
| Tier | Requests / minute | Burst |
|---|---|---|
| Sandbox | 60 | 10/sec |
| Production | 300 | 50/sec |
| Enterprise | Custom | Custom |
Rate limit headers are included in every response:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 298
X-RateLimit-Reset: 1709395200
11. Environments​
| Environment | Base URL | Purpose |
|---|---|---|
| Sandbox | https://sandbox.stealth.health | Testing with mock data, no real doctors or pharmacy |
| Production | https://api.stealth.health | Live environment |
Sandbox behavior:
- Referrals auto-advance through the lifecycle every 60 seconds.
- Payment always succeeds with test card
4242 4242 4242 4242. - Webhooks are sent to the registered sandbox webhook URL.
- No real clinical review occurs.
Appendix: Status Lifecycle​
Terminal states: delivered, denied, cancelled, expired
Questions for Partner Discussion​
- Revenue share model — How will partner compensation be structured? Per-referral fee, revenue share, or flat monthly?
- Branding depth — Does the partner want full white-label (custom domain, emails from their domain) or co-branded?
- Product categories — Which categories does the partner want to offer?
- Jurisdiction — US only, Canada only, or both?
- Patient support — Will the partner handle first-line patient support, or will Stealth Health?
- Reporting needs — Does the partner need additional reporting endpoints beyond the summary endpoint?
- Refund handling — How should refunds be coordinated between partner and Stealth Health?
- Re-enrollment — Should the same customer be able to enroll for additional product categories, and how should that be linked?
This document is a proposal for discussion purposes. Endpoint paths, field names, and behaviors are subject to change during implementation.