Registering an Available Wholesaler

Note

Part of the Wholesaler Integrations guide. See Scope, Webhooks and Errors first for the wholesaler OAuth scope and the webhook delivery/signing mechanics referenced below.

This is the first step of the integration: without a registered AvailableWholesaler, clinics cannot find or link you, and none of the other endpoints in this guide will accept your token for a wholesaler that doesn’t exist yet.

Note

Step 0, before any of this: your application itself must already be registered as an integration — see Adding an Application in Provet. You can’t obtain a wholesaler-scoped access token until that’s done. Consider subscribing to the installation webhook described in Get Notified for Installations of Your Applications in a Provet Organisation so you’re notified as soon as a clinic installs your application, and can automate the rest of onboarding (registering your AvailableWholesaler for them, etc.) from there.

Step 1 — Register an Available Wholesaler

An AvailableWholesaler lives in the clinic’s Provet database and represents a wholesaler integration a specific clinic can later pick from a list. In practice, you create or update this entry once your system is registered as an integration with Provet.

# Create a new available wholesaler.
curl -X POST "https://[env.]provetcloud.com/<provet_id>/api/0.1/available_wholesaler/" \
  -H "Authorization: Bearer <oauth_access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Veterinary Supplies",
    "email": "orders@acmevet.example",
    "register_number": "FI12345678",
    "webhook_callback_url": "https://acmevet.example/webhooks/inbound",
    "verification_salt": "<generated_secret>"
  }'

# List all registered available wholesalers.
curl "https://[env.]provetcloud.com/<provet_id>/api/0.1/available_wholesaler/" \
  -H "Authorization: Bearer <oauth_access_token>"

# Retrieve one by ID.
curl "https://[env.]provetcloud.com/<provet_id>/api/0.1/available_wholesaler/<id>/" \
  -H "Authorization: Bearer <oauth_access_token>"

# Update one (e.g. rotate verification_salt, or update webhook_callback_url).
curl -X PATCH "https://[env.]provetcloud.com/<provet_id>/api/0.1/available_wholesaler/<id>/" \
  -H "Authorization: Bearer <oauth_access_token>" \
  -H "Content-Type: application/json" \
  -d '{"verification_salt": "<new_generated_secret>"}'

# Delete one.
curl -X DELETE "https://[env.]provetcloud.com/<provet_id>/api/0.1/available_wholesaler/<id>/" \
  -H "Authorization: Bearer <oauth_access_token>"

[env.] is the deployment the clinic lives on (e.g. us., enterprise., or omitted for the default environment).

The owning integration is always taken from your access token — a value sent in the request body is ignored, and creating one with a legacy API key instead of an OAuth 2.0 access token answers 401.

Removing a registration

DELETE archives the registration instead of erasing it, and answers 204, also when it was already archived. Archiving is permanent: there is no endpoint to bring the registration back.

  • Every clinic Wholesaler linked to the registration is archived with it. You receive no wholesaler.deleted event for them, since you are the one leaving.

  • You receive no new events, and events still queued for you are dropped, not delivered.

  • The catalog and other wholesaler endpoints answer 404 for those wholesaler.ids.

  • You keep access to the orders you have already received for those wholesalers, so you can finish them: reading an order, answering its transmission status, marking it delivered and setting your reference number keep working. No new orders reach you.

  • The registration still appears in GET responses, and its name stays taken, so you can’t register the same name again.

The same happens when a clinic removes your OAuth 2.0 application or Provet revokes it, except that your access tokens stop working too, so existing orders can’t be updated either. In that case it is reversible: when the application is added again or restored, the registration and exactly the wholesalers archived with it come back, and you receive a fresh wholesaler.created for each wholesaler.id. While your registration is archived, the clinic can’t restore those wholesalers itself.

webhook_callback_url and verification_salt

webhook_callback_url must use HTTPS and point to a publicly reachable host — private, loopback, and localhost addresses are rejected with 400, since deliveries can carry decrypted credentials (see Step 2 — Clinic connects the wholesaler). This applies while you are developing too, so point the URL at a public tunnel to your local receiver, or at a deployed one, rather than at your own machine.

verification_salt is the shared secret used to sign every webhook delivered to that URL — see “Webhook signature verification” in Scope, Webhooks and Errors for the full scheme.

Note

Despite the field name, this is a shared secret, not a salt: it is the HMAC key, it is never sent to you in a delivery, and it must be protected like any other credential. Generate it from a cryptographically secure random source — 32 random bytes rendered as 64 hexadecimal characters is a good default, for example openssl rand -hex 32. Do not derive it from your company name, your client id, or anything else guessable.

In short:

  • Accepted on create and update, as a field on available_wholesaler/.

  • Required on create — a registration cannot be created without one.

  • Write-only — Provet never returns it on any response, so keep your own copy.

  • 16 to 128 characters. Any character is accepted except emoji and other 4-byte characters (see below). It is encoded as UTF-8 to form the HMAC key, so a value outside US-ASCII must be encoded the same way on your side when you verify.

  • Updates take effect atomically for the next delivery, with no grace period, so rotation is coordinated from your side (see Scope, Webhooks and Errors).

These records are later used in two places: clinic staff see them when choosing a wholesaler to connect, and your system authenticates later uploads against the registered AvailableWholesaler.

Unsupported characters

Text values cannot contain emoji or other 4-byte characters (characters outside the Basic Multilingual Plane, such as U+1F600). Creating or updating an available_wholesaler/ or a wholesaler/ with such a character in any text field, department_info included, is rejected with 400 and an error on that field. When a request contains such a character, only these errors are returned; any other validation error is reported once they are fixed:

{"name": ["Contains unsupported characters such as emoji."]}

A filter value on the available_wholesaler/ or wholesaler/ list with such a character is rejected with 400 as well:

["One or more filter values contain unsupported characters."]

Step 2 — Clinic connects the wholesaler

This step is internal to Provet. Clinic staff pick your wholesaler from a list in the UI and link it to their AvailableWholesaler registration, from Stock > Inventory > Wholesalers > Create Wholesaler, selecting “Integration” as the wholesaler type — your available wholesaler is shown as one of the options. See Add Wholesalers for the customer-facing walkthrough of this screen, and Wholesalers in Provet for how clinics manage wholesalers more generally.

Once linked:

  • A Wholesaler is created in the clinic’s database, pointing back at your AvailableWholesaler.

  • Provet POSTs a wholesaler.created event to your webhook_callback_url.

Use the wholesaler.id you receive as the wholesaler_pk for catalog uploads (Making Products Available) and to correlate incoming orders (Handling Orders).

Later on, if clinic staff edit the credentials or clinic location of that Wholesaler, you receive a wholesaler.updated event with the new values; if they remove it, you receive a wholesaler.deleted event for that wholesaler.id. See the event references below.

The id identifies one set of credentials, not the clinic as a whole. Treat every id as a separate account, and expect order.placed to carry the id of the account the ordering location uses. You never need to know whether an id is location-specific or clinic-wide.

Clinic-wide or per-location credentials

A clinic sets up its account with you in one of two ways, never both at once:

  • Clinic-wide. One set of credentials, used by every clinic location. You receive one wholesaler.created with wholesaler.department null. If it carries only null credentials, the clinic has not entered them yet; keep the id, it stays valid.

  • Per location. Each location that orders from you enters its own credentials. You receive one wholesaler.created per location, each with its own wholesaler.id and its wholesaler.department set. More locations can be added at any time, each arriving as one more wholesaler.created.

Credentials can identify a physical site with its own delivery address, and clinic locations can be far apart, so Provet keeps no clinic-wide credentials once any location has its own.

When a clinic switches between the two, you hear about it as ids being retired and issued, never as an update:

  • Clinic-wide to per location: wholesaler.deleted for the clinic-wide id and wholesaler.created for the location that switched. The clinic-wide credentials are cleared.

  • Per location to clinic-wide: wholesaler.deleted for the last location id and wholesaler.created for a new clinic-wide id.

The two events are delivered independently and can arrive in either order, so handle each one on its own: a wholesaler.created for a new id does not mean the retired id is gone yet, nor the other way round.

A location removing its own credentials while others keep theirs sends only wholesaler.deleted for its id.

Archiving a clinic location removes its credentials in the same way: you receive wholesaler.deleted for its id and, if it was the last location with its own credentials, wholesaler.created for a new clinic-wide id. If the clinic has archived your wholesaler at the time, these events are sent when it is restored, alongside the wholesaler.created events for its remaining ids.

Note

The catalog belongs to a credential set, not to the clinic as a whole: uploading against one id replaces the catalog only for the locations that order under that id. A location whose id has no upload of its own sees no catalog, even if a sibling id of the same clinic (including the clinic-wide one) does. Upload against every id you have been given.

wholesaler.created 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 a clinic connects the wholesaler. It only fires for integration wholesalers, so it always describes a Wholesaler created from one of your AvailableWholesaler registrations. Any field the clinic left blank is sent as null.

{
  "event": "wholesaler.created",
  "event_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
  "available_wholesaler": {
    "id": 7,
    "name": "Acme Veterinary Supplies",
    "email": "orders@acmevet.example",
    "register_number": "FI12345678"
  },
  "wholesaler": {
    "id": 123,
    "customer_number": "CLINIC-42",
    "customer_username": "clinic42-login",
    "customer_password": "s3cr3t",
    "department": {"id": 4, "name": "Main Clinic"}
  }
}

Field

Type

Optional

Meaning

event

string

No

Always "wholesaler.created".

event_id

string

No

UUID identifying this event. See deduplication rules.

available_wholesaler.id

number

No

ID of your registered AvailableWholesaler.

available_wholesaler.name

string

No

Name of that AvailableWholesaler.

available_wholesaler.email

string / null

Yes

Contact email registered for the AvailableWholesaler.

available_wholesaler.register_number

string / null

Yes

Business / registration number registered for the AvailableWholesaler.

wholesaler.id

number

No

Identifies this set of credentials — the wholesaler_pk you use for catalog uploads. One per event; a clinic with location-specific credentials sends one event, and one id, per location.

wholesaler.customer_number

string / null

Yes

The clinic’s customer number with the wholesaler.

wholesaler.customer_username

string / null

Yes

Login / username the clinic entered for this wholesaler.

wholesaler.customer_password

string / null

Yes

Password the clinic entered, decrypted. Treat it as a secret.

wholesaler.department

object / null

Yes

Set when credentials are specific to one clinic location; null when they apply wholesaler-wide. Contains id and name of the Department / Clinic Location.

Note

name, email, and register_number live under available_wholesaler because they describe the integration you registered. For an integration wholesaler the clinic cannot edit them, so they are not repeated on the wholesaler object.

Provet customers can choose to enable wholesalers globally in their organization, or locally in specific clinic locations / departments. The credential fields carry department-specific values when department is present, otherwise the wholesaler-global values.

wholesaler.updated 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 when clinic staff edit the credentials, or the clinic location, of a Wholesaler created from one of your AvailableWholesaler registrations. Only a real change is delivered — editing the wholesaler’s name or address sends nothing, since that data lives on available_wholesaler and cannot be edited by the clinic anyway.

A clinic location entering its own credentials for the first time is not an update: that creates a new credential set, which reaches you as wholesaler.created under its own wholesaler.id.

The body repeats the wholesaler.created shape carrying the new values, so one handler can serve both events; only event tells them apart. Credentials are reported as the editing clinic location sees them, so an edit to a location-specific override carries wholesaler.department, exactly as it would for wholesaler.created.

Important

One edit can update several ids. Clinic locations that were given the same username share a single account with you, so Provet keeps their password identical: changing it for one location changes it for every location using that username. Each of those is a credential set of its own, so you receive one wholesaler.updated per affected wholesaler.id, not just for the location the clinic was editing.

Expect, therefore, to receive an update for an id nobody appears to have touched, and apply each event to the id it names rather than assuming a clinic edit only concerns one account. Locations set up with different usernames are separate accounts and are never changed by an edit to another location.

{
  "event": "wholesaler.updated",
  "event_id": "5a4f0e1a-1b2c-4d3e-9f8a-7c6b5a4d3e2f",
  "available_wholesaler": {
    "id": 7,
    "name": "Acme Veterinary Supplies",
    "email": "orders@acmevet.example",
    "register_number": "FI12345678"
  },
  "wholesaler": {
    "id": 123,
    "customer_number": "CLINIC-42-NEW",
    "customer_username": "clinic42-login",
    "customer_password": "n3w-s3cr3t",
    "department": {"id": 4, "name": "Main Clinic"}
  }
}

Fields have the same meaning as on wholesaler.created — see the table above.

wholesaler.deleted 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 when clinic staff remove (archive) a Wholesaler created from one of your AvailableWholesaler registrations. Stop pushing catalogs for that wholesaler.id — the clinic keeps its purchase history, so past orders remain valid. Removing the whole wholesaler sends you one event, and one id, per location: the same set of ids wholesaler.created gave you.

It also fires for a single id on its own, when the clinic retires one credential set while keeping the link: a location gives up its own credentials, or the clinic switches between clinic-wide and per-location credentials (see Clinic-wide or per-location credentials). The retired id is dead and is never reissued.

If the clinic connects again later, you receive a fresh wholesaler.created for each wholesaler.id, which you should treat exactly as a first connection. The id may be one you had retired: restoring a link keeps its ids, connecting again from scratch gets new ones. Match on the id you are given.

{
  "event": "wholesaler.deleted",
  "event_id": "9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
  "available_wholesaler": {
    "id": 7,
    "name": "Acme Veterinary Supplies",
    "email": "orders@acmevet.example",
    "register_number": "FI12345678"
  },
  "wholesaler": {
    "id": 123
  }
}

Field

Type

Optional

Meaning

event

string

No

Always "wholesaler.deleted".

event_id

string

No

UUID identifying this event. See deduplication rules.

available_wholesaler.id

number

No

ID of your registered AvailableWholesaler.

available_wholesaler.name

string

No

Name of that AvailableWholesaler.

available_wholesaler.email

string / null

Yes

Contact email registered for the AvailableWholesaler.

available_wholesaler.register_number

string / null

Yes

Business / registration number registered for the AvailableWholesaler.

wholesaler.id

number

No

The id of the credential set that was removed — the same one wholesaler.created gave you for it.

Note

No credentials are sent — they have no bearing on a removal.

Errors specific to registration

Situation

Response

OAuth token invalid/expired

401

Update / delete on an available_wholesaler registered by a different OAuth application

403 Token does not match this wholesaler.

Creating an available_wholesaler with a legacy API key instead of an OAuth2 token

401

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