Developers

MWS Connect Partner Integration Guide

Version 1 (v1) · Last updated October 18, 2026

MWS Connect is the standard way for a pharmacy system to book MWS deliveries and receive live delivery status. Your system makes three HTTPS calls to MWS; MWS sends status updates back to one URL you host.

How it works

An order is booked the moment your system sends it; you do not need to call MWS again unless it is cancelled.

  1. Register the recipient

    Your system sends the patient's name, phone and address (POST /v1/partner/client).

  2. Send the order

    Once it is packed and ready for pickup (POST /v1/partner/order). MWS checks it straight away and replies 200 (accepted) or 400 (with the exact problem).

  3. MWS books the delivery

    For pickup at your pharmacy, within the delivery window you gave.

  4. MWS sends status updates

    To your webhook URL as the order moves: picked up, out for delivery, delivered, or not delivered with a reason.

  5. Cancel if needed

    Before pickup (POST /v1/partner/order/cancel). A CANCELLED update follows.

All calls are HTTPS with JSON bodies. Your references (recipient ID and order ID) are the keys throughout; you never need to store an MWS ID.

Environments and access

Each partner gets a sandbox and a production environment, with separate keys. Build and test in sandbox; MWS switches you to production only after the test plan passes.

ItemSandboxProduction
Base URLhttps://connect-api-sandbox.mwscares.com/v1https://connect-api.mwscares.com/v1
API keyYour own sandbox keyYour own production key
Webhook signing secretYour own sandbox secretYour own production secret
Your webhook URLA test URL you give MWSYour live URL
DeliveriesTest only, never dispatchedReal drivers
  • Keys and secrets are sent by one-time secure link, never by email. Store them as secrets, not in code.
  • Authentication: send your key in the x-mws-api-key header on every call. A missing or wrong key returns 401.
  • IP allowlisting (optional): status updates come from one fixed address, 184.193.175.142. Allowlist it if your firewall or hosting (for example, a bot checkpoint) blocks unknown callers.
  • Times are 24-hour Eastern (America/Toronto) for delivery windows, and UTC ISO 8601 for timestamps.

Paths in this guide follow the base URL, for example https://connect-api.mwscares.com/v1/partner/order. New optional fields may be added within v1; ignore fields you do not recognise. Anything that would break your integration comes as a new version, with notice.

Register a recipient

POST/v1/partner/client

Creates or updates the person receiving the delivery. Send it before their first order, and again whenever their details change; the same external_reference_id updates the existing record.

FieldRules
external_reference_id
Required
Your patient or client ID. Up to 100 characters.
first_name
Required
Up to 100 characters.
last_name
Required
Up to 100 characters. If you hold a single name, send a placeholder such as -.
phone
Required
Valid 10-digit North American number; +1 and formatting are fine. The driver calls this number.
address
Required
Full street address incl. unit, city, postal code. Up to 300 characters.
email
Optional
Valid email address, up to 254 characters.
updated_at
Optional
When the record last changed in your system, UTC ISO 8601.
JSON
{
  "external_reference_id": "PT-10482",
  "first_name": "Jane",
  "last_name": "Doe",
  "phone": "416-555-0142",
  "email": "jane@example.com",
  "address": "100 Main St, Unit 4, Mississauga, ON L5B 1A1",
  "updated_at": "2026-10-18T14:05:00Z"
}

Reply 200: {"status": "received", "external_reference_id": "PT-10482"}.

Create a delivery order

POST/v1/partner/order

Books one delivery. Send it when the order is packed and ready for pickup; the recipient must already be registered.

FieldRules
order_external_reference
Required
Your order ID, unique per delivery. Up to 100 characters. Status updates and cancels use it.
client_external_reference
Required
The recipient's external_reference_id.
destination_address
Required
Where to deliver, incl. unit, city, postal code. Up to 300 characters.
delivery_date
Required
YYYY-MM-DD. Today up to 8 days ahead; not in the past.
time_window_start
Required
HH:MM, 24-hour Eastern.
time_window_end
Required
HH:MM, later than the start.
delivery_options
OptionalComing soon
List of option codes from the table below. Omit or send [] for none.
cod_amount
OptionalComing soon
Cash the driver collects at the door, in CAD, as "12.50". Omit or "0.00" when prepaid. Cash only.
location_id
OptionalComing soon
Which of your stores the driver picks up from. Needed only if MWS has set up more than one pickup location for you; MWS gives you the IDs. Otherwise pickup is your registered address.
item_notes
Optional
Instructions for the driver, up to 1,000 characters. No medication names or clinical details.
delivery_type
Optional
DELIVERY if sent (the only type today).
status
Optional
READY_FOR_HAULAGE if sent.
updated_at
Optional
UTC ISO 8601.

Delivery options Coming soon

CodeMeaning for the driver
SIGNATURE_REQUIREDRecipient must sign
NARCOTICContains a narcotic or controlled drug; MWS controlled-drug handling applies
REFRIGERATEDKeep cold; deliver from the fridge bag
URGENTPriority order
ASAPDeliver as soon as possible after pickup
CALL_ON_ARRIVALPhone the recipient on arrival
RING_BELLRing the bell
LEAVE_AT_DOORMay be left at the door if no answer
DO_NOT_LEAVE_AT_DOORNever leave unattended
LEAVE_IN_MAILBOXMay be left in the mailbox
MULTIPLE_BOXESMore than one box; check the count at pickup
COLLECT_FORMBring back a form signed by the recipient
COLLECT_PRESCRIPTIONCollect a paper prescription from the recipient

Unknown codes, and contradictory pairs such as LEAVE_AT_DOOR with DO_NOT_LEAVE_AT_DOOR or with SIGNATURE_REQUIRED, are rejected with 400.

JSON
{
  "order_external_reference": "RX-2026-55871",
  "client_external_reference": "PT-10482",
  "destination_address": "100 Main St, Unit 4, Mississauga, ON L5B 1A1",
  "delivery_date": "2026-10-19",
  "time_window_start": "14:00",
  "time_window_end": "18:00",
  "delivery_options": ["SIGNATURE_REQUIRED", "REFRIGERATED"],
  "cod_amount": "12.50",
  "item_notes": "Buzz 104."
}

Reply 200: {"status": "received", "order_external_reference": "RX-2026-55871"}. The order is booked within seconds.

  • Resending is safe. The same order_external_reference sent twice books one delivery; the repeat changes nothing.
  • To change an order (address, date, window, options, amount), cancel it and send it again under a new order_external_reference.
  • Cash orders: if the recipient cannot pay, the driver does not leave the order; you receive NOT_DELIVERED with the reason.

Cancel an order

POST/v1/partner/order/cancel

Cancels a delivery that has not been picked up yet. The answer is immediate, and a CANCELLED status update follows.

JSON
{
  "order_external_reference": "RX-2026-55871",
  "reason": "Patient picked up in store"
}

reason is optional (up to 500 characters) and kept for MWS records.

ReplyBody status / meaningWhat to do
200cancellation_requested: cancelledNothing; a CANCELLED update follows
200already_cancelledNothing; safe to resend
404Order reference not foundCheck the reference
409Already picked up by a driverCall MWS dispatch to stop the delivery
503Order still being booked, or briefly unavailableRetry after the Retry-After seconds (30)

Status updates

MWS posts each status change to your webhook URL as JSON. The first update arrives at pickup; there is no update when the order is booked.

StatusMeaning
PICKED_UPDriver has collected the order from your pharmacy
AT_WAREHOUSEHeld at the MWS depot (for example, overnight before a next-day route)
OUT_FOR_DELIVERYOn the driver's route to the recipient
DELIVEREDHanded over
NOT_DELIVEREDAttempt failed; failure_reason says why. A later attempt may follow
CANCELLEDCancelled by you or by MWS
JSON
{
  "event_id": "6f1c2a9e-8b3d-5f47-9a10-2c4e7d8b1f35",
  "order_external_reference": "RX-2026-55871",
  "status": "NOT_DELIVERED",
  "occurred_at": "2026-10-19T19:42:10.000Z",
  "tracking_url": "https://...",
  "failure_reason": "Customer didn't answer"
}
  • Verify every call. The x-mws-signature header is the HMAC-SHA256 of the exact raw request body, keyed with your webhook secret, in lowercase hex. Compute it over the raw bytes before parsing, and compare in constant time. Reject a mismatch with 401.
  • Reply 2xx within 10 seconds. Save the update and process it afterwards if needed.
  • Retries: any other reply, or a timeout, is retried after 30 s, 1 min, 2 min and so on, at most 2 h apart, for about 24 hours. MWS is alerted if delivery still fails.
  • De-duplicate on event_id. A retried update keeps the same event_id; ignore one you have already stored.
  • Order by occurred_at. Retries can arrive late, so never let an older update overwrite a newer one.
  • failure_reason is filled only on NOT_DELIVERED. tracking_url may be empty.
Python
import hmac, hashlib

def is_from_mws(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

Errors and retries

A 400 lists every problem at once, so one fix-and-resend is usually enough.

JSON
{
  "error": "Invalid request: phone must be a valid 10-digit North American number; delivery_date is in the past",
  "problems": [
    "phone must be a valid 10-digit North American number",
    "delivery_date is in the past"
  ]
}
ReplyMeaningWhat your system should do
200AcceptedDone
400Invalid or malformed requestFix the data and resend; do not retry unchanged
401Missing or wrong API keyCheck the key and environment
404Wrong path, or unknown order (cancel)Check the URL or reference
409Cancel too late: already picked upCall MWS dispatch
4295xxor timeoutTemporaryRetry with growing gaps (for example 30 s, 1 min, 2 min); resending is safe

Staff should see 400 problems in your system, so a bad phone number or address can be corrected at the counter.

Testing and go-live

The sandbox test plan below is the same one completed by our live partner; it usually takes one or two working sessions together.

Sandbox test plan

  • Register a recipient and create orders with and without delivery options, a cash amount and (if you have several stores) a location: all return 200 and show correctly on the booking
  • Send deliberately bad requests (invalid phone, past date, missing field, wrong key): each returns the expected 400 or 401 with the right problem
  • Resend an accepted order unchanged: 200, no second delivery
  • MWS moves a test order through PICKED_UP, OUT_FOR_DELIVERY and DELIVERED: your endpoint returns 2xx and verifies every signature
  • MWS sends NOT_DELIVERED with a reason: it shows correctly in your system
  • A repeated update (same event_id) is ignored
  • Cancel a fresh order: 200 cancellation_requested, then a CANCELLED update arrives
  • Cancel it again: 200 already_cancelled

Go-live checklist

  • MWS confirms the sandbox results
  • You send your production webhook URL; MWS sends your production key and secret by one-time link
  • Allowlist 184.193.175.142 if your hosting filters callers
  • Agree the cutover date, and stop booking the same orders by phone, email or portal from that date to avoid double deliveries
  • First live order followed end to end by both teams

Contact

Integration questions and sandbox access: contact@mwscares.com. Same-day delivery issues: MWS dispatch.

Request sandbox access

contact@mwscares.com

Changelog

DateVersionChange
October 18, 2026v1First public release.