How an integration fits together
Two decisions shape the whole integration, and both are easy to get wrong in a way that only shows up in production. This page is about those two.
One token per vet per practice
A connection token identifies a vet and the practice their notes belong to. That is why no request you make carries a user identifier or a practice identifier: the token already says both.
The consequence is the part that catches people. A vet who works at two practices connects twice and ends up holding two tokens, one per practice. If you key your stored token on the user alone, the second connection overwrites the first, and that first practice's notes silently stop working.
So store the token against your user and the site the consult belongs to. When you need to tell two tokens for the same vet apart, read practice_name from getConnection.
What stops a token
A connection token does not expire on a timer. That is deliberate: a vet forced to re-authorise in the middle of a consult is a worse failure than a long-lived credential. It stops when the vet disconnects, leaves the practice, or their account is deleted, and it stops if we disable your integration.
Those two causes need different handling, and the difference matters because one is recoverable by the vet and the other is not. The Unauthorized response spells out which code means which, and the remedy for each.
The key is the address
We do not mint an identifier for a note. The Idempotency-Key you send when you submit a consult is the note's address, and it is what you pass to the polling endpoint.
This is unusual enough to be worth spelling out, because it changes one line of your code and removes a whole class of failure:
Write the key to your consult record before you call us, not after you get our response.
Do that and a lost response costs you nothing. Our acknowledgement can vanish in transit, your process can restart mid-request, the connection can drop after we accepted the audio, and the note is still reachable, because you never needed anything we sent back in order to find it.
Store it afterwards instead and a dropped response means a consult that exists on our side, was paid for, and that you have no way to ask about.
Retrying with the same key returns the original note rather than generating a second one. A genuinely new consult must use a new key.
Your own reference
external_ref is separate and serves a different purpose. It is your identifier for the consultation, stored and echoed back on every response for that note, and it is the fastest way for us to find a note in a support conversation. We never interpret it. Send it whenever you have one.
What runs where
Setting up a practice, joining an existing one, and subscribing all happen on WisePaws. None of it touches your product, and there is no API for any of it. Your integration covers exactly two things: connecting a vet once, and turning a consult into a note.
The connect step is the only part that is a browser navigation rather than an API call, and it is the only place a vet sees WisePaws. Everything after it is server to server.
A vet who is new to WisePaws
A vet with no WisePaws practice yet sorts one out before they can approve, all inside that same connect step: they sign up, then either set up a practice and subscribe, or join one that already has a subscription. Once the practice can record, WisePaws brings them straight back to approve the connection, and only then are they returned to you.
That can take several minutes, and it can include a payment. Two things on your side decide whether that vet gets through:
- Keep the
stateyou sent valid for at least two hours, not the few minutes that suits a vet who is already set up. WisePaws holds their place for an hour from when they start setting up, and signing up comes before that. - Tie
stateto the browser, not to one tab or window. Verifying their email opens a new tab, and the vet may finish there, so they can come back to yourredirect_uriin a different tab from the one you opened. Checkstateagainst a cookie set withSameSite=Lax, notStrict: the redirect back to you is a navigation from another site, and aStrictcookie is not sent with it. Handle the redirect as an ordinary full page rather than throughwindow.opener, even if you started the flow in a popup.
Get either wrong and you will reject the one vet who did everything right.
If they abandon setup part-way, or join a practice that cannot record yet, nothing comes back to you. The connection simply never completes, and they can start again from your product.
Where to look next
- When a consult fails, which is where the real product decisions are.
- The API reference, generated from the specification. When anything on these guide pages disagrees with it, the reference is right and we have a bug to fix.