ELMED is currently in early access, we're on boarding our first doctors, pharmacies, and patients. Some features are still being refined. What this means for you →

ELMED / Webhook Docs

Outbound Webhooks

ELMED can push events to your system as they happen — no polling required. Works for any HTTPS endpoint: your PMS, pharmacy POS, medical aid admin system, or a custom integration.

Events

Event When it fires Who subscribes
prescription.issued Doctor writes a new prescription Doctor, Pharmacy
prescription.dispensed Pharmacy marks a script as dispensed Pharmacy, Doctor
consultation.completed Doctor marks a consultation as complete Doctor
order.status_changed Pharmacy updates an order status (dispensing, in transit, delivered, etc.) Pharmacy
claim.decision Medical aid approves, partially approves, or rejects a claim Medical Aid
referral.created A specialist referral is created Doctor, Specialist

Payload format

All webhooks are HTTP POST requests with a JSON body and Content-Type: application/json.

The top-level structure is always:

{
  "event": "prescription.issued",
  "timestamp": "2026-03-29T10:45:00.123456",
  "data": { ... }
}

prescription.issued

{
  "event": "prescription.issued",
  "timestamp": "2026-03-29T10:45:00",
  "data": {
    "prescription_id": 42,
    "prescription_number": "RX-2026-00042",
    "patient_id": 7,
    "doctor_user_id": 3,
    "routed_pharmacy_id": 1,
    "medication_count": 2,
    "issued_at": "2026-03-29"
  }
}

prescription.dispensed

{
  "event": "prescription.dispensed",
  "timestamp": "2026-03-29T11:00:00",
  "data": {
    "prescription_id": 42,
    "patient_id": 7,
    "pharmacy_id": 1,
    "pharmacist_user_id": 5,
    "collection_method": "collect"
  }
}

consultation.completed

{
  "event": "consultation.completed",
  "timestamp": "2026-03-29T10:30:00",
  "data": {
    "consultation_id": 15,
    "patient_id": 7,
    "doctor_user_id": 3,
    "type": "video",
    "completed_at": "2026-03-29T10:29:55"
  }
}

order.status_changed

{
  "event": "order.status_changed",
  "timestamp": "2026-03-29T12:00:00",
  "data": {
    "order_id": 88,
    "order_number": "ORD-2026-00088",
    "patient_id": 7,
    "status": "IN_TRANSIT",
    "pharmacy_id": 1,
    "tracking_number": "CPT123456",
    "courier": "The Courier Guy"
  }
}

claim.decision

{
  "event": "claim.decision",
  "timestamp": "2026-03-29T14:00:00",
  "data": {
    "claim_id": 31,
    "patient_id": 7,
    "claim_type": "consultation",
    "status": "approved",
    "amount_claimed": 850.00,
    "amount_approved": 850.00,
    "rejection_reason": null,
    "scheme_id": 2
  }
}

Signature verification

Every request includes two extra headers:

  • X-ELMED-Signature: sha256=<hex> — HMAC-SHA256 of the raw request body, signed with your endpoint's secret
  • X-ELMED-Event: <event_type> — the event name

Always verify the signature before processing the payload. Your secret is shown (truncated) in the webhooks settings page — it's generated when you add the endpoint.

Python

import hmac, hashlib

def verify_signature(secret: str, body: bytes, header: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)

# In your Flask/FastAPI handler:
body = request.get_data()  # raw bytes
sig = request.headers.get("X-ELMED-Signature", "")
if not verify_signature(YOUR_SECRET, body, sig):
    return 401

Node.js

const crypto = require("crypto");

function verifySignature(secret, body, header) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}

// In your Express handler:
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["x-elmed-signature"] || "";
  if (!verifySignature(YOUR_SECRET, req.body, sig)) return res.sendStatus(401);
  const { event, data } = JSON.parse(req.body);
  // handle event...
  res.sendStatus(200);
});

PHP

$body    = file_get_contents("php://input");
$header  = $_SERVER["HTTP_X_ELMED_SIGNATURE"] ?? "";
$expected = "sha256=" . hash_hmac("sha256", $body, YOUR_SECRET);

if (!hash_equals($expected, $header)) {
    http_response_code(401);
    exit;
}

$payload = json_decode($body, true);
// handle $payload["event"] ...

Retries

If your endpoint returns a non-2xx status or times out (10 second timeout), ELMED will retry automatically:

Attempt Delay
1stImmediate
2nd30 seconds
3rd5 minutes

After 3 failed attempts the delivery is marked as failed. You can view all delivery attempts and error messages in the Delivery Log inside your portal.

Your endpoint should return 200 as quickly as possible and process the event asynchronously. If you need more than a few hundred milliseconds to handle a webhook, put it in a queue and return 200 immediately.

Setting up

Webhooks are managed per portal. Log in and go to Webhooks in the sidebar:

Add your endpoint URL, choose which events to subscribe to, and save. Your unique signing secret is generated automatically and shown once — copy it and store it safely in your environment variables.

Testing your endpoint

The easiest way to test is to use webhook.site — paste the unique URL it gives you as your endpoint in ELMED, then trigger a real event (complete a consultation, dispense a script, etc.) and watch the payload arrive in real time.

Once you see the expected payload, copy the signature header and run it through your verification code before connecting your production endpoint.

Your endpoint must be publicly reachable over HTTPS. Local localhost addresses will fail. For local development, use a tunnel tool like ngrok (ngrok http 3000) to get a public HTTPS URL that forwards to your local server.
Questions? Email support@elmed.healthcare