Skip to main content

Real-Time Event Notifications

Webhooks

Subscribe to webhook events to get instant notifications when drug prices change, recalls are issued, generics become available, and more. No polling required.

Quick Setup

Get webhook notifications flowing to your application in three steps.

1
Register an Endpoint
Provide an HTTPS URL where you want to receive webhook events. Your endpoint must return a 2xx status within 30 seconds.
POST /v1/webhooks
{
  "url": "https://yourapp.com/api/webhooks/tm",
  "events": ["price.changed", "drug.recalled"],
  "description": "Production webhook"
}
2
Verify Signatures
Each webhook request includes a signature header. Verify it to ensure the payload is authentic and untampered.
// Response includes your signing secret:
{
  "id": "wh_abc123",
  "secret": "whsec_a1b2c3d4e5f6...",
  "url": "https://yourapp.com/api/webhooks/tm",
  "events": ["price.changed", "drug.recalled"],
  "active": true
}
3
Handle Events
Parse the incoming JSON payload, verify the signature, process the event, and return a 200 status code.
// Express.js example
app.post("/api/webhooks/tm", (req, res) => {
  const signature = req.headers["x-tm-signature"];
  const timestamp = req.headers["x-tm-timestamp"];

  if (!verifySignature(req.body, signature, timestamp)) {
    return res.status(401).send("Invalid signature");
  }

  const event = req.body;
  switch (event.event) {
    case "price.changed":
      handlePriceChange(event.data);
      break;
    case "drug.recalled":
      handleRecall(event.data);
      break;
  }

  res.status(200).json({ received: true });
});

Available Events

Subscribe to any combination of the following events. You can update your subscriptions at any time via the API or dashboard.

price.changed
Price changes by more than $0.01 or 0.5% (whichever is greater) at any tracked pharmacy.

Triggered when the price of a monitored drug changes at any pharmacy. Includes the old price, new price, percentage change, and the pharmacy details.

Payload Schema

{
  "event": "price.changed",
  "id": "evt_a1b2c3d4e5f6",
  "created_at": "2026-04-05T14:30:00Z",
  "data": {
    "drug": {
      "id": "drug_12345",
      "name": "Metformin 500mg",
      "din": "02248573"
    },
    "pharmacy": {
      "id": "pharm_67890",
      "name": "Shoppers Drug Mart #1234",
      "city": "Toronto",
      "province": "ON"
    },
    "old_price": 12.99,
    "new_price": 10.49,
    "change_amount": -2.50,
    "change_percent": -19.25,
    "currency": "CAD",
    "effective_date": "2026-04-05"
  }
}
drug.recalled
A new recall notice is published by Health Canada for any drug in the TransparentMedz database.

Triggered when Health Canada issues a recall for a drug in the database. Includes recall classification, affected lots, and recommended actions.

Payload Schema

{
  "event": "drug.recalled",
  "id": "evt_r3c4l5a6b7c8",
  "created_at": "2026-04-05T09:15:00Z",
  "data": {
    "drug": {
      "id": "drug_54321",
      "name": "Valsartan 80mg",
      "din": "02303310"
    },
    "recall": {
      "id": "recall_hc_2026_0412",
      "classification": "Type I",
      "reason": "Potential NDMA contamination above acceptable limits",
      "affected_lots": ["LOT2025A", "LOT2025B", "LOT2025C"],
      "manufacturer": "Generic Pharma Inc.",
      "health_canada_url": "https://recalls-rappels.canada.ca/en/alert-recall/...",
      "action_required": "Stop dispensing affected lots. Patients should contact their pharmacist.",
      "issued_date": "2026-04-05"
    }
  }
}
generic.available
A new generic version of a monitored brand-name drug is added to the database with confirmed pharmacy availability.

Triggered when a new generic alternative becomes available for a brand-name drug. Includes pricing comparison and the list of pharmacies that carry it.

Payload Schema

{
  "event": "generic.available",
  "id": "evt_g1e2n3r4i5c6",
  "created_at": "2026-04-05T11:00:00Z",
  "data": {
    "brand_drug": {
      "id": "drug_11111",
      "name": "Lipitor 20mg",
      "din": "02230711",
      "average_price": 89.99
    },
    "generic_drug": {
      "id": "drug_22222",
      "name": "Atorvastatin 20mg",
      "din": "02401142",
      "manufacturer": "Apotex Inc.",
      "average_price": 24.99,
      "savings_percent": 72.2
    },
    "available_at_pharmacies": 1247,
    "provinces_available": ["ON", "BC", "AB", "QC", "MB", "SK", "NS", "NB"]
  }
}
pharmacy.updated
Any change to a pharmacy record including hours, contact info, services, or operational status.

Triggered when pharmacy details change, such as hours, contact information, services offered, or when a pharmacy opens or closes permanently.

Payload Schema

{
  "event": "pharmacy.updated",
  "id": "evt_p1h2r3m4u5p6",
  "created_at": "2026-04-05T16:45:00Z",
  "data": {
    "pharmacy": {
      "id": "pharm_67890",
      "name": "Shoppers Drug Mart #1234",
      "city": "Toronto",
      "province": "ON"
    },
    "changes": [
      {
        "field": "hours.saturday",
        "old_value": "09:00-18:00",
        "new_value": "09:00-21:00"
      },
      {
        "field": "services",
        "added": ["flu_vaccination", "covid_testing"],
        "removed": []
      }
    ],
    "updated_at": "2026-04-05T16:45:00Z"
  }
}
shortage.reported
A new drug shortage is reported to the Drug Shortages Canada database, or an existing shortage status changes.

Triggered when a drug shortage is reported or updated. Includes severity level, estimated resolution date, and alternative medications if available.

Payload Schema

{
  "event": "shortage.reported",
  "id": "evt_s1h2o3r4t5g6",
  "created_at": "2026-04-05T08:30:00Z",
  "data": {
    "drug": {
      "id": "drug_33333",
      "name": "Amoxicillin 250mg/5mL Suspension",
      "din": "00628018"
    },
    "shortage": {
      "id": "shortage_2026_0405",
      "severity": "critical",
      "status": "active",
      "reason": "Manufacturing delay",
      "affected_provinces": ["ON", "QC", "BC", "AB"],
      "estimated_resolution": "2026-05-15",
      "alternatives": [
        { "din": "02412345", "name": "Amoxicillin 500mg Capsules" },
        { "din": "02412346", "name": "Cephalexin 250mg/5mL Suspension" }
      ],
      "reported_date": "2026-04-05",
      "source": "Drug Shortages Canada"
    }
  }
}

Retry Policy

If your endpoint returns a non-2xx status code or times out (30 second limit), we will retry delivery using an exponential backoff schedule. After all retries are exhausted, the event is marked as failed and available in your dashboard for manual replay.

AttemptDelay
#1
Immediate
#2
1 minute
#3
5 minutes
#4
30 minutes
#5
2 hours
#6
8 hours
#7
24 hours

Events that fail all 7 retry attempts are stored for 30 days and can be manually replayed from your developer dashboard.

Security

Every webhook request is signed with your unique signing secret. Always verify signatures before processing events.

Signature Verification
Verify the HMAC-SHA256 signature included in every webhook request.

Headers Included

  • X-TM-Signature
    HMAC-SHA256 hex digest
  • X-TM-Timestamp
    Unix timestamp (seconds)
  • X-TM-Event
    Event type name
Verification Example
import crypto from "crypto";

function verifySignature(
  payload: string,
  signature: string,
  timestamp: string,
  secret: string
): boolean {
  // Reject requests older than 5 minutes
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    return false;
  }

  const signedPayload = `${timestamp}.${payload}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signedPayload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
Always verify signatures before processing
Reject requests with timestamps older than 5 minutes
Use constant-time comparison to prevent timing attacks
Serve your endpoint over HTTPS only
Return 200 quickly, process asynchronously

Start Receiving Events

Set up your first webhook in under 5 minutes. All plans include webhook access with unlimited event subscriptions.