Guides · PNE
Webhooks
Webhooks let your integration be notified the moment a record is created or updated in PatientNow Essentials — no polling required. You register a URL for the events you care about; when a matching event fires, PatientNow sends an HTTP POST to that URL with a small JSON notification.
Webhooks are a PNE feature. They are not part of the VISH or PatientNow Pro
surfaces. Everything on this page targets the PNE base URL
https://api.envisiongo.com/api/v1.
Overview
A webhook is a registration that pairs an event name (such as
Appointment.Created) with a receiver URL you control.
Register one webhook per event you want to receive. When that event occurs,
PatientNow delivers a compact notification — event metadata plus a URL that points
at the affected record — to your receiver. The full record is not embedded
in the notification; you fetch it from the provided URL using your normal API
credentials.
Patient.* event names
The API resource is customers, but the webhook events for it are
named Patient.Created and Patient.Updated. There is no
Customer.Created event — see Supported event
names.
How delivery works
- You register a receiver URL for an event.
- Something happens in PatientNow — a customer, appointment, or order is created or updated.
- PatientNow sends an HTTP POST with a JSON notification to your URL.
- Your receiver returns a
2xxquickly to acknowledge receipt. - You use the
Urlfrom the notification to fetch the full record from the API.
PatientNow has no test tenant. Any record you create to trigger a test webhook is created in real data. Use a disposable receiver (see Testing), but create test records with care.
Supported event names
These are the only event names accepted when registering a webhook. They are matched case-insensitively but are shown here in their canonical form. Register the specific events you want to receive.
| Event name | Fires when… | Record the Url points to |
|---|---|---|
Patient.Created | A customer is added | customers/{id} |
Patient.Updated | A customer is updated | customers/{id} |
Appointment.Created | An appointment is added | appointments/{id}/details |
Appointment.Updated | An appointment is updated | appointments/{id}/details |
Order.Created | An order is added | orders/{id} |
Order.Updated | An order is updated | orders/{id} |
If you only need to know when records first appear, register the three
*.Created events: Patient.Created,
Appointment.Created, and Order.Created.
Register a webhook
POST /api/v1/webhooks
Send a JSON body pairing an event name with your receiver URL. Repeat the call once per event you want to subscribe to.
Request body
| Field | Type | Description | |
|---|---|---|---|
EventName |
string | required | One of the supported event names. An
unrecognized value is rejected with 400. |
WebhookUrl |
string | required | Absolute http or https URL that will receive
the notification. Must be publicly reachable. |
curl -X POST \
"https://api.envisiongo.com/api/v1/webhooks?apikey=YOUR_API_KEY" \
-H "Authorization: Basic $(printf 'USERNAME:PASSWORD' | base64)" \
-H "Content-Type: application/json" \
-d '{
"EventName": "Appointment.Created",
"WebhookUrl": "https://example.com/webhooks/patientnow"
}'const res = await fetch(
"https://api.envisiongo.com/api/v1/webhooks?apikey=YOUR_API_KEY",
{
method: "POST",
headers: {
"Authorization": "Basic " + btoa("USERNAME:PASSWORD"),
"Content-Type": "application/json",
},
body: JSON.stringify({
EventName: "Appointment.Created",
WebhookUrl: "https://example.com/webhooks/patientnow",
}),
}
);
const webhook = await res.json();import base64, requests
token = base64.b64encode(b"USERNAME:PASSWORD").decode()
res = requests.post(
"https://api.envisiongo.com/api/v1/webhooks",
params={"apikey": "YOUR_API_KEY"},
headers={"Authorization": f"Basic {token}"},
json={
"EventName": "Appointment.Created",
"WebhookUrl": "https://example.com/webhooks/patientnow",
},
)
webhook = res.json()using var client = new HttpClient();
var token = Convert.ToBase64String(
Encoding.UTF8.GetBytes("USERNAME:PASSWORD"));
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", token);
var body = new StringContent(
"{\"EventName\":\"Appointment.Created\"," +
"\"WebhookUrl\":\"https://example.com/webhooks/patientnow\"}",
Encoding.UTF8, "application/json");
var res = await client.PostAsync(
"https://api.envisiongo.com/api/v1/webhooks?apikey=YOUR_API_KEY",
body);Response
A successful registration returns the stored webhook record.
{
"Id": 4271,
"WebhookUrl": "https://example.com/webhooks/patientnow",
"EventType": "Appointment.Created"
}Id is an integer
Unlike PNE record IDs — which are opaque encrypted strings — a webhook
registration's Id is a plain integer. You use it to
fetch or delete the registration.
List registered webhooks
GET /api/v1/webhooks
Returns every webhook registered for your tenant. Two optional query filters narrow the list.
| Parameter | Type | Description | |
|---|---|---|---|
EventType |
string | optional | Return only registrations for this event. Must be a
supported event name; an invalid value returns
400. |
WebhookUrl |
string | optional | Return only registrations for this receiver URL. Must be an absolute
http/https URL. |
curl -X GET \
"https://api.envisiongo.com/api/v1/webhooks?apikey=YOUR_API_KEY&EventType=Appointment.Created" \
-H "Authorization: Basic $(printf 'USERNAME:PASSWORD' | base64)"const res = await fetch(
"https://api.envisiongo.com/api/v1/webhooks?apikey=YOUR_API_KEY",
{ headers: { "Authorization": "Basic " + btoa("USERNAME:PASSWORD") } }
);
const webhooks = await res.json();import base64, requests
token = base64.b64encode(b"USERNAME:PASSWORD").decode()
res = requests.get(
"https://api.envisiongo.com/api/v1/webhooks",
params={"apikey": "YOUR_API_KEY"},
headers={"Authorization": f"Basic {token}"},
)
webhooks = res.json()using var client = new HttpClient();
var token = Convert.ToBase64String(
Encoding.UTF8.GetBytes("USERNAME:PASSWORD"));
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", token);
var res = await client.GetAsync(
"https://api.envisiongo.com/api/v1/webhooks?apikey=YOUR_API_KEY");Response
[
{
"Id": 4271,
"WebhookUrl": "https://example.com/webhooks/patientnow",
"EventType": "Appointment.Created"
},
{
"Id": 4272,
"WebhookUrl": "https://example.com/webhooks/patientnow",
"EventType": "Patient.Created"
}
]Get a single webhook
GET /api/v1/webhooks/{id}
Retrieve one registration by its integer Id. A non-integer (or
non-positive) id returns 400; an id that
isn't registered to your tenant returns 404.
curl -X GET \
"https://api.envisiongo.com/api/v1/webhooks/4271?apikey=YOUR_API_KEY" \
-H "Authorization: Basic $(printf 'USERNAME:PASSWORD' | base64)"{
"Id": 4271,
"WebhookUrl": "https://example.com/webhooks/patientnow",
"EventType": "Appointment.Created"
}Delete a webhook
DELETE /api/v1/webhooks/{id}
Remove a registration by its integer Id. The same validation applies:
a bad id returns 400, an unknown one 404. A
successful delete returns 204 No Content.
curl -X DELETE \
"https://api.envisiongo.com/api/v1/webhooks/4271?apikey=YOUR_API_KEY" \
-H "Authorization: Basic $(printf 'USERNAME:PASSWORD' | base64)"The delivery payload
When an event fires, your receiver gets an HTTP POST with a JSON body like this:
{
"Application": "PNE",
"TenantId": "NA01_12345",
"EventId": "0c6f94f8-4d14-41c0-b8f8-8e52d7d8c111",
"EventType": "Appointment.Created",
"EventTimestamp": "2026-06-02T15:30:00Z",
"Url": "https://api.envisiongo.com/api/v1/appointments/{id}/details"
}| Field | Type | Description |
|---|---|---|
Application |
string | Source application identifier — PNE. |
TenantId |
string | Your tenant, in server-node-prefix_tenant-id form
(e.g. NA01_12345). |
EventId |
string | Unique GUID for this notification. Store it to deduplicate. |
EventType |
string | The event that occurred, e.g. Appointment.Created. |
EventTimestamp |
string | UTC timestamp (ISO 8601) when the notification was created. |
Url |
string | The API URL to call to fetch the full affected record. |
Retrieving the full record
The notification carries metadata and a pointer, not the record itself. Call the
Url from the payload with your normal PNE credentials — remember to
append the gateway apikey, which is not part of the delivered
Url.
curl -X GET \
"https://api.envisiongo.com/api/v1/appointments/{id}/details?apikey=YOUR_API_KEY" \
-H "Authorization: Basic $(printf 'USERNAME:PASSWORD' | base64)"Url are opaque
The record identifiers embedded in the Url (the {id}
segment) are opaque encrypted strings — pass them back exactly as received. See
Guides: IDs.
Testing webhooks
To confirm end-to-end delivery without standing up a server, point a webhook at a disposable receiver such as webhook.site, then create a matching record.
- Open webhook.site and copy the unique URL it generates for you.
- Register that URL for the event you want to test —
for example
Patient.Created. - Confirm it appears in your webhook list.
- Create the matching record in PatientNow (a customer for
Patient.Created, an appointment forAppointment.Created, an order forOrder.Created). - Watch webhook.site for the incoming POST.
Confirm the body is JSON,
EventTypematches, andEventId,EventTimestamp, andUrlare present. - Copy the
Urland call it with your API credentials to confirm it returns the record you created.
webhook.site URLs are public and temporary. Never register one for a production integration, and remember there is no sandbox — the record you create to trigger the test is real.
Receiver requirements
Your webhook endpoint should:
- Accept HTTPS POST requests with JSON bodies.
- Return a
2xxresponse promptly on receipt. If you need to do more work, store the event and process it asynchronously. - Be idempotent — store the
EventIdand ignore a notification whoseEventIdyou've already processed. - Use the
Urlfield to fetch the full record rather than expecting it inline.
Each delivery attempt has a 15-second timeout. If your endpoint
hasn't returned a 2xx within that window, the attempt is treated as
failed. Acknowledge fast and defer heavy processing — including the follow-up
record fetch — to a background job.
A delivery that times out or returns a non-2xx response is retried.
PatientNow waits 30 seconds between attempts and makes up to
10 attempts in total before giving up. Because a retried
notification carries the same EventId, an idempotent receiver will
safely ignore the duplicate.
A dependable receiver typically: parses the payload → validates
EventType → checks whether EventId was already seen →
stores the event → returns 200 OK → then fetches and processes the
full record from Url.
Troubleshooting
Registered, but no event arrives
- The registered
EventNameexactly matches a supported event name. - The receiver URL is publicly reachable and uses
httporhttps. - Your endpoint returns a
2xx. - The record action actually completed — a failed create fires nothing.
The event type is rejected at registration
Use one of the exact supported event names. In
particular, there is no Customer.Created — use
Patient.Created.
The payload doesn't contain the full record
That's expected. The notification carries metadata and a Url; call
the Url with your credentials to retrieve the
record.
Duplicate notifications
Receivers should be idempotent. Store the EventId and skip any you've
already handled.
Quick reference
| Task | Method | Endpoint |
|---|---|---|
| Register webhook | POST | /api/v1/webhooks |
| List webhooks | GET | /api/v1/webhooks |
| Get webhook by ID | GET | /api/v1/webhooks/{id} |
| Delete webhook | DELETE | /api/v1/webhooks/{id} |