set -eu
umask 077
# Path values must be URL-encoded; query string excludes the leading question mark.
url="${HD_BASE_URL:?Set API origin}/v1/partner/webhook-endpoints/${HD_QUERY_STRING:+?${HD_QUERY_STRING}}"
printf 'Accept: application/json\n' > "${HD_REQUEST_HEADERS_FILE:?Private header file}"
printf 'Authorization: Bearer %s\n' "${HD_PARTNER_API_KEY:?Set partner API key}" >> "$HD_REQUEST_HEADERS_FILE"
curl --silent --show-error --max-time 30 --request POST \
--header "@$HD_REQUEST_HEADERS_FILE" \
--dump-header "${HD_HEADERS_FILE:?Private response header file}" \
--output "${HD_RESPONSE_FILE:?Private response file}" \
--write-out '%{http_code}\n%{content_type}\n' \
--header 'Content-Type: application/json' \
--data-binary "@${HD_REQUEST_FILE:?JSON fixture file}" \
"$url"import os, json
from pathlib import Path
from urllib.parse import quote
import requests
os.umask(0o077)
url = os.environ["HD_BASE_URL"].rstrip("/") + '/v1/partner/webhook-endpoints/'
if os.environ.get("HD_QUERY_STRING"): url += "?" + os.environ["HD_QUERY_STRING"]
headers = {"Accept": 'application/json'}
headers["Authorization"] = "Bearer " + os.environ["HD_PARTNER_API_KEY"]
kwargs = {}
kwargs["json"] = json.loads(Path(os.environ["HD_REQUEST_FILE"]).read_text())
response = requests.request('POST', url, headers=headers, timeout=30, allow_redirects=False, **kwargs)
Path(os.environ["HD_RESPONSE_FILE"]).write_bytes(response.content)
Path(os.environ["HD_HEADERS_FILE"]).write_text(json.dumps(dict(response.headers)))
print(response.status_code)
print(response.headers.get("Content-Type", ""))
import { readFileSync, writeFileSync } from "node:fs";
const env = process.env;
let url = env.HD_BASE_URL.replace(/\/$/, "") + "/v1/partner/webhook-endpoints/";
if (env.HD_QUERY_STRING) url += "?" + env.HD_QUERY_STRING;
const headers = { "Accept": "application/json" };
headers.Authorization = "Bearer " + env.HD_PARTNER_API_KEY;
const options = { method: "POST", headers, redirect: "manual", signal: AbortSignal.timeout(30000) };
headers["Content-Type"] = "application/json";
options.body = readFileSync(env.HD_REQUEST_FILE, "utf8");
const response = await fetch(url, options);
writeFileSync(env.HD_RESPONSE_FILE, Buffer.from(await response.arrayBuffer()), {mode: 0o600});
writeFileSync(env.HD_HEADERS_FILE, JSON.stringify(Object.fromEntries(response.headers)), {mode: 0o600});
console.log(response.status);
console.log(response.headers.get("content-type") ?? "");
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hi-doctor.ai/v1/partner/webhook-endpoints/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://partner.example.com/hooks/hi-doctor',
'events' => [
'consultation.approved',
'consultation.rejected',
'prescription.issued'
],
'description' => 'Order system'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hi-doctor.ai/v1/partner/webhook-endpoints/"
payload := strings.NewReader("{\n \"url\": \"https://partner.example.com/hooks/hi-doctor\",\n \"events\": [\n \"consultation.approved\",\n \"consultation.rejected\",\n \"prescription.issued\"\n ],\n \"description\": \"Order system\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hi-doctor.ai/v1/partner/webhook-endpoints/")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"https://partner.example.com/hooks/hi-doctor\",\n \"events\": [\n \"consultation.approved\",\n \"consultation.rejected\",\n \"prescription.issued\"\n ],\n \"description\": \"Order system\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hi-doctor.ai/v1/partner/webhook-endpoints/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"https://partner.example.com/hooks/hi-doctor\",\n \"events\": [\n \"consultation.approved\",\n \"consultation.rejected\",\n \"prescription.issued\"\n ],\n \"description\": \"Order system\"\n}"
response = http.request(request)
puts response.read_body{
"id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"object": "webhook_endpoint",
"url": "https://partner.example.com/hooks/hi-doctor",
"description": "Order system",
"mode": "test",
"events": [
"consultation.approved",
"consultation.rejected",
"prescription.issued"
],
"status": "enabled",
"failure_streak": 0,
"last_success_at": null,
"last_failure_at": null,
"previous_secret_expires_at": null,
"created_at": "2026-11-03T10:15:00Z",
"updated_at": "2026-11-03T10:15:00Z",
"secret": "whsec_Q2hhbmdlTWVJbkFSZWFsU2VjcmV0U3RvcmUxMjM0NTY="
}Create a webhook endpoint
Registers an https URL on port 443 to receive the listed event types, or [”*”] for all of them: consultation.approved, consultation.cancelled, consultation.in_review, consultation.info_requested, consultation.rejected, consultation.submitted, consultation.updated, prescription.cancelled, prescription.issued, prescription.replaced. endpoint.test is sent only by POST /webhook-endpoints//test and cannot be subscribed to (422 invalid_webhook_events). mode, when sent, must be the key’s own mode (422 mode_mismatch). The response carries the signing secret once. Requires the consultations:read, prescriptions:read and webhooks:manage scopes.
Access, effects and retries
Ownership: The API key’s own partner and mode (test or live).
Prerequisites: A partner API key with the webhooks:manage and consultations:read and prescriptions:read scopes.
Effects: Registers the endpoint and creates its signing secret, returned once.
Retries: Not replayed by Idempotency-Key: a retry registers a second endpoint.
set -eu
umask 077
# Path values must be URL-encoded; query string excludes the leading question mark.
url="${HD_BASE_URL:?Set API origin}/v1/partner/webhook-endpoints/${HD_QUERY_STRING:+?${HD_QUERY_STRING}}"
printf 'Accept: application/json\n' > "${HD_REQUEST_HEADERS_FILE:?Private header file}"
printf 'Authorization: Bearer %s\n' "${HD_PARTNER_API_KEY:?Set partner API key}" >> "$HD_REQUEST_HEADERS_FILE"
curl --silent --show-error --max-time 30 --request POST \
--header "@$HD_REQUEST_HEADERS_FILE" \
--dump-header "${HD_HEADERS_FILE:?Private response header file}" \
--output "${HD_RESPONSE_FILE:?Private response file}" \
--write-out '%{http_code}\n%{content_type}\n' \
--header 'Content-Type: application/json' \
--data-binary "@${HD_REQUEST_FILE:?JSON fixture file}" \
"$url"import os, json
from pathlib import Path
from urllib.parse import quote
import requests
os.umask(0o077)
url = os.environ["HD_BASE_URL"].rstrip("/") + '/v1/partner/webhook-endpoints/'
if os.environ.get("HD_QUERY_STRING"): url += "?" + os.environ["HD_QUERY_STRING"]
headers = {"Accept": 'application/json'}
headers["Authorization"] = "Bearer " + os.environ["HD_PARTNER_API_KEY"]
kwargs = {}
kwargs["json"] = json.loads(Path(os.environ["HD_REQUEST_FILE"]).read_text())
response = requests.request('POST', url, headers=headers, timeout=30, allow_redirects=False, **kwargs)
Path(os.environ["HD_RESPONSE_FILE"]).write_bytes(response.content)
Path(os.environ["HD_HEADERS_FILE"]).write_text(json.dumps(dict(response.headers)))
print(response.status_code)
print(response.headers.get("Content-Type", ""))
import { readFileSync, writeFileSync } from "node:fs";
const env = process.env;
let url = env.HD_BASE_URL.replace(/\/$/, "") + "/v1/partner/webhook-endpoints/";
if (env.HD_QUERY_STRING) url += "?" + env.HD_QUERY_STRING;
const headers = { "Accept": "application/json" };
headers.Authorization = "Bearer " + env.HD_PARTNER_API_KEY;
const options = { method: "POST", headers, redirect: "manual", signal: AbortSignal.timeout(30000) };
headers["Content-Type"] = "application/json";
options.body = readFileSync(env.HD_REQUEST_FILE, "utf8");
const response = await fetch(url, options);
writeFileSync(env.HD_RESPONSE_FILE, Buffer.from(await response.arrayBuffer()), {mode: 0o600});
writeFileSync(env.HD_HEADERS_FILE, JSON.stringify(Object.fromEntries(response.headers)), {mode: 0o600});
console.log(response.status);
console.log(response.headers.get("content-type") ?? "");
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hi-doctor.ai/v1/partner/webhook-endpoints/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://partner.example.com/hooks/hi-doctor',
'events' => [
'consultation.approved',
'consultation.rejected',
'prescription.issued'
],
'description' => 'Order system'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hi-doctor.ai/v1/partner/webhook-endpoints/"
payload := strings.NewReader("{\n \"url\": \"https://partner.example.com/hooks/hi-doctor\",\n \"events\": [\n \"consultation.approved\",\n \"consultation.rejected\",\n \"prescription.issued\"\n ],\n \"description\": \"Order system\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hi-doctor.ai/v1/partner/webhook-endpoints/")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"https://partner.example.com/hooks/hi-doctor\",\n \"events\": [\n \"consultation.approved\",\n \"consultation.rejected\",\n \"prescription.issued\"\n ],\n \"description\": \"Order system\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hi-doctor.ai/v1/partner/webhook-endpoints/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"https://partner.example.com/hooks/hi-doctor\",\n \"events\": [\n \"consultation.approved\",\n \"consultation.rejected\",\n \"prescription.issued\"\n ],\n \"description\": \"Order system\"\n}"
response = http.request(request)
puts response.read_body{
"id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"object": "webhook_endpoint",
"url": "https://partner.example.com/hooks/hi-doctor",
"description": "Order system",
"mode": "test",
"events": [
"consultation.approved",
"consultation.rejected",
"prescription.issued"
],
"status": "enabled",
"failure_streak": 0,
"last_success_at": null,
"last_failure_at": null,
"previous_secret_expires_at": null,
"created_at": "2026-11-03T10:15:00Z",
"updated_at": "2026-11-03T10:15:00Z",
"secret": "whsec_Q2hhbmdlTWVJbkFSZWFsU2VjcmV0U3RvcmUxMjM0NTY="
}Authorizations
Partner API key sent as Authorization: Bearer . Test keys and live keys are separate.
Headers
Accepted but not used for replay on this operation. Only POST /v1/partner/consultations stores and replays responses by Idempotency-Key.
^[A-Za-z0-9_-]{8,128}$Body
The https URL (port 443) that receives deliveries.
1 - 2048\S[""] for every event, or a non-empty list of event types; "" cannot be combined with event types and duplicates are ignored. A value that is not a list, a blank entry or an entry longer than 48 characters is refused first with 422 invalid_request; a list of strings that is not ["*"] or a non-empty list of documented event types is then refused with 422 invalid_webhook_events.
1* Your description of the endpoint.
200test or live. Defaults to the API key mode; another mode is refused with 422 mode_mismatch.
live, test Response
The new endpoint and its secret.
The webhook endpoint id.
Always webhook_endpoint.
webhook_endpoint The https URL (port 443) that receives deliveries.
Your description of the endpoint.
test or live, from the API key that created it.
live, test Event types sent to the endpoint, or ["*"] for all of them.
enabled, disabled or auto_disabled.
enabled, disabled, auto_disabled Consecutive failed deliveries; reset by a successful delivery or by re-enabling the endpoint.
When a delivery to the endpoint last succeeded.
When a delivery to the endpoint last failed.
After a rotation, when the previous secret stops signing deliveries; otherwise null.
When it was created.
When it last changed.
The signing secret: whsec_ followed by the standard base64 encoding of 32 random bytes, which are the HMAC key. It is shown only in this response; store it now.