Handling Orders

Note

Part of the Wholesaler Integrations guide. This step assumes you have already completed Registering an Available Wholesaler — orders only arrive for clinics that have connected your wholesaler.

See Create an Order for how a clinic places the purchase order that triggers order.placed below.

order.placed event reference

Note

This event is also published in the API reference, whose machine-readable payload schema you can generate a client from.

Provet POSTs this event to available_wholesaler.webhook_callback_url when clinic staff send a purchase order to a wholesaler of type Integration created from one of your AvailableWholesaler registrations. It does not fire for wholesalers of any other type (e.g. Email).

When the order is sent, Provet optimistically marks it as ordered and delivers this event; the payload is richer than a typical order notification so you can act on the order without follow-up calls.

Warning

order.placed may be delivered more than once for the same order.id — either as a redundant retry of the same event (identified by a repeated event_id, safe to drop), or as a genuinely new event for the same order under a different event_id. The latter doesn’t necessarily mean a brand new order was placed again — the clinic may simply have updated the existing order. Use event_id only to discard redundant deliveries; when a new event_id arrives for an order.id you have already processed, compare the payload against the version you are holding and act on whatever changed. Each delivery carries the complete order, so the payload alone tells you what is different.

{
  "event": "order.placed",
  "event_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "available_wholesaler": {"id": 7, "name": "Acme Veterinary Supplies"},
  "wholesaler": {"id": 123, "customer_number": "CLINIC-42"},
  "order": {
    "id": 5567,
    "notes": "Please deliver before noon",
    "customer": {
      "name": "Downtown Vet Group",
      "department": "Main Clinic",
      "postal_address": "12 Market Street",
      "postal_code": "00100",
      "city": "Helsinki",
      "country_code": "FI"
    },
    "purchaser": {
      "name": "Jane Doe",
      "phone": "+358401234567",
      "email": "jane.doe@downtownvet.example"
    },
    "items": [
      {
        "product_number": "ACM-001",
        "product_name": "Amoxicillin 250mg",
        "quantity": "12.00",
        "unit": "tablet",
        "barcode": "6412345678901",
        "notes": null
      }
    ]
  }
}

Field

Type

Optional

Meaning

event

string

No

Always "order.placed".

event_id

string

No

UUID identifying this event; stable across delivery attempts. See deduplication rules.

available_wholesaler.id

number

No

ID of your registered AvailableWholesaler.

available_wholesaler.name

string

No

Name of that AvailableWholesaler.

wholesaler.id

number

No

Per-clinic Wholesaler ID — the same wholesaler_pk used for catalog uploads and returned by wholesaler.created.

order.id

number

No

Per-clinic Order ID. Identifies the order in the calls below.

order.customer

object

No

The ordering clinic’s billing/delivery identity.

order.purchaser

object

No

The user who sent the order (name, phone, email — phone/email fall back from department contact to the user’s own).

order.items[]

array

No

Order lines: product_number (your code, null if the item has none — match on product_name/barcode instead), product_name, quantity (decimal string), unit, barcode, notes.

wholesaler.customer_number

string / null

Yes

The clinic’s customer number with you; department override if set, else wholesaler-global; null if not set.

order.notes

string / null

Yes

Free-text note the clinic added to the order.

Recording your own order number

When you accept an order you normally assign it a supplier order number of your own. Write it back so clinic staff can quote it to you and see it on the order:

curl -X POST \
  "https://[env.]provetcloud.com/<provet_id>/api/0.1/order/<order_id>/set_wholesaler_reference_number/" \
  -H "Authorization: Bearer <oauth_access_token>" \
  -H "Content-Type: application/json" \
  -d '{"wholesaler_reference_number": "SO123456"}'
{
  "status": "ok",
  "wholesaler_reference_number": "SO123456"
}

The wholesaler scope is sufficient — no additional Provet permission is needed. The value is limited to 63 characters and appears on the order in Provet as its Wholesaler invoice number.

  • Your own orders only. An order placed with a wholesaler belonging to another integration, or with no integration wholesaler behind it, is rejected with 403 (Token does not match this wholesaler.).

  • The order must already be placed; one still in Ordering in process is 400.

Last write wins. If the order already carries a reference number — clinic staff can enter one themselves before placing the order — yours replaces it. The response always echoes the value now stored.

The call is safe to retry: sending the same value again changes nothing and answers the same 200.

Marking an order delivered

Once you have shipped an order received via order.placed, move it out of Ordered yourself:

curl -X POST \
  "https://[env.]provetcloud.com/<provet_id>/api/0.1/order/<order_id>/mark_delivered/" \
  -H "Authorization: Bearer <oauth_access_token>"

The wholesaler scope is sufficient — no additional Provet permission is needed. The call runs exactly what the clinic’s own “Mark products delivered” button runs:

{
  "status": "ok",
  "order_status": 2,
  "all_delivered": true,
  "delivery_date": "2026-08-12T09:15:00Z"
}
  • Your own orders only. An order placed with a wholesaler belonging to another integration, or with no integration wholesaler behind it, is rejected with 403 (Token does not match this wholesaler.).

  • The order must be in Ordered status; anything else is 400.

set_wholesaler_reference_number and mark_delivered are the only order actions the wholesaler scope opens up. Every other order operation stays behind the clinic’s own permissions.

Retrying a call that timed out

The call is not idempotent: it only moves an order out of Ordered, so calling it a second time on an order it already moved answers 400, not a repeat of the original success.

That makes a timed-out request safe to retry, as long as you read the failure correctly:

  • 400 with Order must be in 'Ordered' status to mark products delivered. — the order is no longer in Ordered, so your earlier call landed. Treat your retry as complete; there is nothing further to do.

  • Any other 400 — a genuine failure, reporting a problem with the order’s contents. The order is still in Ordered and still awaiting delivery, so this one does need your attention. Do not treat it as success.

Concurrent duplicate calls are safe: the order row is locked for the duration, so two requests arriving together are applied one after the other, and the second gets the 400 above rather than repeating the stock changes.

Errors specific to orders

Situation

Response

mark_delivered or set_wholesaler_reference_number on an order placed with another application’s wholesaler, or with no integration wholesaler at all

403 Token does not match this wholesaler.

mark_delivered on an order that is not in Ordered status

400

set_wholesaler_reference_number on an order still in Ordering in process

400

set_wholesaler_reference_number with a missing, empty or over-63-character value

400

See the error reference for the integration-specific errors that can occur across every phase.