The ProviderDownload API gives you real-time access to the full NPPES dataset — 7+ million NPI records with taxonomy, practice locations, and monthly change tracking.
For an interactive explorer, see the Swagger UI.
All requests require an API key passed in the X-Api-Key header. Get a key by signing up.
curl https://api.providerdownload.com/v1/providers/1234567890 \ -H "X-Api-Key: YOUR_API_KEY"
import requests
headers = {"X-Api-Key": "YOUR_API_KEY"}
r = requests.get(
"https://api.providerdownload.com/v1/providers/1234567890",
headers=headers
)
print(r.json())const res = await fetch("https://api.providerdownload.com/v1/providers/1234567890", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const data = await res.json();
console.log(data);const https = require("https");
const options = {
hostname: "api.providerdownload.com",
path: "/v1/providers/1234567890",
headers: { "X-Api-Key": "YOUR_API_KEY" }
};
https.get(options, res => {
let body = "";
res.on("data", chunk => body += chunk);
res.on("end", () => console.log(JSON.parse(body)));
});<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1234567890");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
print_r($data);using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");
var response = await client.GetStringAsync(
"https://api.providerdownload.com/v1/providers/1234567890");
Console.WriteLine(response);All endpoints are HTTPS only. Responses are JSON.
| Status | Meaning |
|---|---|
401 | Missing or invalid API key |
404 | NPI not found |
422 | Invalid request parameters |
429 | Monthly quota exceeded |
500 | Internal server error |
| Plan | Monthly requests | Bulk NPIs per call |
|---|---|---|
| Free | 1,000 | 100 |
| Starter | 10,000 | 500 |
| Pro | 100,000 | 1,000 |
Check your current usage at GET /account/me. When you're close to the limit, the response includes a quota_warning message.
Returns full details for a single NPI number.
curl https://api.providerdownload.com/v1/providers/1003000118 \ -H "X-Api-Key: YOUR_API_KEY"
import requests
headers = {"X-Api-Key": "YOUR_API_KEY"}
r = requests.get(
"https://api.providerdownload.com/v1/providers/1003000118",
headers=headers
)
provider = r.json()
print(provider["first_name"], provider["last_name"])const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const provider = await res.json();
console.log(provider.first_name, provider.last_name);const https = require("https");
https.get({
hostname: "api.providerdownload.com",
path: "/v1/providers/1003000118",
headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
let body = "";
res.on("data", c => body += c);
res.on("end", () => console.log(JSON.parse(body)));
});<?php
$npi = "1003000118";
$ch = curl_init("https://api.providerdownload.com/v1/providers/{$npi}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $data["first_name"] . " " . $data["last_name"];using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");
var json = await client.GetStringAsync(
"https://api.providerdownload.com/v1/providers/1003000118");
Console.WriteLine(json);Search and filter the full NPI registry. All parameters are optional.
| Parameter | Type | Description |
|---|---|---|
name | string | Last name or organization name (partial match) |
first_name | string | First name (individuals only, partial match) |
entity_type | string | individual or organization |
status | string | active or deactivated |
state | string | Two-letter practice state (e.g. FL) |
city | string | Practice city |
zip | string | Practice ZIP code |
taxonomy | string | Exact NUCC taxonomy code |
specialty | string | Specialty name search (partial match) |
gender | string | M or F |
page | integer | Page number (default: 1) |
limit | integer | Results per page, max 200 (default: 20) |
# Search by last name and state curl "https://api.providerdownload.com/v1/providers?name=smith&state=FL&limit=10" \ -H "X-Api-Key: YOUR_API_KEY" # Search by first and last name curl "https://api.providerdownload.com/v1/providers?name=smith&first_name=john&state=FL" \ -H "X-Api-Key: YOUR_API_KEY" # Search by specialty curl "https://api.providerdownload.com/v1/providers?specialty=cardiology&state=FL&limit=10" \ -H "X-Api-Key: YOUR_API_KEY"
import requests
r = requests.get(
"https://api.providerdownload.com/v1/providers",
headers={"X-Api-Key": "YOUR_API_KEY"},
params={"name": "smith", "first_name": "john", "state": "FL", "limit": 10}
)
results = r.json()
for p in results["results"]:
print(p["npi"], p.get("first_name"), p.get("last_name") or p.get("organization_name"))const params = new URLSearchParams({ name: "smith", first_name: "john", state: "FL", limit: 10 });
const res = await fetch(`https://api.providerdownload.com/v1/providers?${params}`, {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const { results } = await res.json();
results.forEach(p => console.log(p.npi, p.first_name, p.last_name || p.organization_name));const https = require("https");
const qs = require("querystring");
const query = qs.stringify({ specialty: "cardiology", state: "FL", limit: 10 });
https.get({
hostname: "api.providerdownload.com",
path: `/v1/providers?${query}`,
headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
let body = "";
res.on("data", c => body += c);
res.on("end", () => console.log(JSON.parse(body)));
});<?php
$params = http_build_query(["specialty" => "cardiology", "state" => "FL", "limit" => 10]);
$ch = curl_init("https://api.providerdownload.com/v1/providers?{$params}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$results = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($results as $p) {
echo $p["npi"] . "\n";
}using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");
var url = "https://api.providerdownload.com/v1/providers?specialty=cardiology&state=FL&limit=10";
var json = await client.GetStringAsync(url);
Console.WriteLine(json);Resolve up to 1,000 NPIs in a single request. Optionally embed sub-resources (taxonomy, locations, endpoints, names) in each result.
| Field | Type | Description |
|---|---|---|
npis required | string[] | Array of 10-digit NPI numbers |
include | string[] | Sub-resources to embed: taxonomy, locations, endpoints, names |
curl -X POST https://api.providerdownload.com/v1/bulk/lookup \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"npis": ["1003000118", "1003001074"], "include": ["taxonomy"]}'import requests
r = requests.post(
"https://api.providerdownload.com/v1/bulk/lookup",
headers={"X-Api-Key": "YOUR_API_KEY"},
json={
"npis": ["1003000118", "1003001074"],
"include": ["taxonomy"]
}
)
for provider in r.json():
print(provider["npi"], provider.get("taxonomy"))const res = await fetch("https://api.providerdownload.com/v1/bulk/lookup", {
method: "POST",
headers: {
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
npis: ["1003000118", "1003001074"],
include: ["taxonomy"]
})
});
const results = await res.json();
results.forEach(p => console.log(p.npi, p.taxonomy));const https = require("https");
const body = JSON.stringify({
npis: ["1003000118", "1003001074"],
include: ["taxonomy"]
});
const req = https.request({
hostname: "api.providerdownload.com",
path: "/v1/bulk/lookup",
method: "POST",
headers: {
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(body)
}
}, res => {
let data = "";
res.on("data", c => data += c);
res.on("end", () => console.log(JSON.parse(data)));
});
req.write(body);
req.end();<?php
$payload = json_encode([
"npis" => ["1003000118", "1003001074"],
"include" => ["taxonomy"]
]);
$ch = curl_init("https://api.providerdownload.com/v1/bulk/lookup");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"X-Api-Key: YOUR_API_KEY",
"Content-Type: application/json"
]);
$results = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($results as $p) {
echo $p["npi"] . "\n";
}using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");
var payload = new {
npis = new[] { "1003000118", "1003001074" },
include = new[] { "taxonomy" }
};
var content = new StringContent(
System.Text.Json.JsonSerializer.Serialize(payload),
System.Text.Encoding.UTF8,
"application/json"
);
var response = await client.PostAsync(
"https://api.providerdownload.com/v1/bulk/lookup", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());Returns all taxonomy codes for a provider, including primary designation, license numbers, and NUCC descriptions.
curl https://api.providerdownload.com/v1/providers/1003000118/taxonomy \ -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
"https://api.providerdownload.com/v1/providers/1003000118/taxonomy",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
for code in r.json():
print(code["code"], code["display_name"], "primary:", code["primary"])const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118/taxonomy", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const codes = await res.json();
codes.forEach(c => console.log(c.code, c.display_name));https.get({
hostname: "api.providerdownload.com",
path: "/v1/providers/1003000118/taxonomy",
headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
let body = "";
res.on("data", c => body += c);
res.on("end", () => console.log(JSON.parse(body)));
});<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1003000118/taxonomy");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$codes = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($codes as $c) {
echo $c["code"] . " " . $c["display_name"] . "\n";
}var json = await client.GetStringAsync(
"https://api.providerdownload.com/v1/providers/1003000118/taxonomy");
Console.WriteLine(json);Returns secondary practice locations for a provider. The primary practice address is included in the main provider record.
curl https://api.providerdownload.com/v1/providers/1003000118/locations \ -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
"https://api.providerdownload.com/v1/providers/1003000118/locations",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
for loc in r.json():
print(loc["address_line1"], loc["city"], loc["state"])const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118/locations", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const locations = await res.json();
locations.forEach(l => console.log(l.address_line1, l.city, l.state));https.get({
hostname: "api.providerdownload.com",
path: "/v1/providers/1003000118/locations",
headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
let body = "";
res.on("data", c => body += c);
res.on("end", () => console.log(JSON.parse(body)));
});<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1003000118/locations");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$locs = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($locs as $l) {
echo $l["address_line1"] . ", " . $l["city"] . "\n";
}var json = await client.GetStringAsync(
"https://api.providerdownload.com/v1/providers/1003000118/locations");
Console.WriteLine(json);Returns Health Information Exchange (HIE) endpoints — Direct Messaging addresses, FHIR endpoints, SOAP URLs, and more.
curl https://api.providerdownload.com/v1/providers/1003000118/endpoints \ -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
"https://api.providerdownload.com/v1/providers/1003000118/endpoints",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
print(r.json())const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118/endpoints", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
console.log(await res.json());https.get({
hostname: "api.providerdownload.com",
path: "/v1/providers/1003000118/endpoints",
headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
let body = "";
res.on("data", c => body += c);
res.on("end", () => console.log(JSON.parse(body)));
});<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1003000118/endpoints");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
print_r(json_decode(curl_exec($ch), true));
curl_close($ch);var json = await client.GetStringAsync(
"https://api.providerdownload.com/v1/providers/1003000118/endpoints");
Console.WriteLine(json);Returns every name combination ever recorded for a provider, including legal names and other names (former names, maiden names, etc.).
curl https://api.providerdownload.com/v1/providers/1003000118/names \ -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
"https://api.providerdownload.com/v1/providers/1003000118/names",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
print(r.json())const res = await fetch("https://api.providerdownload.com/v1/providers/1003000118/names", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
console.log(await res.json());https.get({
hostname: "api.providerdownload.com",
path: "/v1/providers/1003000118/names",
headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
let body = "";
res.on("data", c => body += c);
res.on("end", () => console.log(JSON.parse(body)));
});<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/1003000118/names");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
print_r(json_decode(curl_exec($ch), true));
curl_close($ch);var json = await client.GetStringAsync(
"https://api.providerdownload.com/v1/providers/1003000118/names");
Console.WriteLine(json);The umbrella change feed. Returns all providers whose status, name, or record changed on or after since. The change_type field is one of: new (first seen), deactivated, reactivated, or updated. For more targeted pulls use /deactivated, /name-changes, or /updated.
next_cursor value from each response as the cursor= parameter on the next request. Cursor-based pagination is stable — it won't skip or duplicate rows if data updates between pages. Offset-based page= pagination is still supported but can drift on large result sets.| Parameter | Type | Description |
|---|---|---|
since | date | Required. Return changes on or after this date (YYYY-MM-DD) |
cursor | string | Opaque cursor from the previous page's next_cursor. Overrides page=. |
page | integer | Page number (default: 1). Use cursor= for large result sets. |
limit | integer | Results per page, max 1000 (default: 100) |
curl "https://api.providerdownload.com/v1/providers/changes?limit=50" \ -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
"https://api.providerdownload.com/v1/providers/changes",
headers={"X-Api-Key": "YOUR_API_KEY"},
params={"limit": 50}
)
print(r.json())const res = await fetch("https://api.providerdownload.com/v1/providers/changes?limit=50", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
console.log(await res.json());https.get({
hostname: "api.providerdownload.com",
path: "/v1/providers/changes?limit=50",
headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
let body = "";
res.on("data", c => body += c);
res.on("end", () => console.log(JSON.parse(body)));
});<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/changes?limit=50");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
print_r(json_decode(curl_exec($ch), true));
curl_close($ch);var json = await client.GetStringAsync(
"https://api.providerdownload.com/v1/providers/changes?limit=50");
Console.WriteLine(json);Returns NPIs that were deactivated. Filter by since/until date range, state, entity type, or taxonomy. Supports cursor pagination via next_cursor / cursor=.
curl "https://api.providerdownload.com/v1/providers/deactivated?limit=50" \ -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
"https://api.providerdownload.com/v1/providers/deactivated",
headers={"X-Api-Key": "YOUR_API_KEY"},
params={"limit": 50}
)
print(r.json())const res = await fetch("https://api.providerdownload.com/v1/providers/deactivated?limit=50", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
console.log(await res.json());https.get({
hostname: "api.providerdownload.com",
path: "/v1/providers/deactivated?limit=50",
headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
let body = "";
res.on("data", c => body += c);
res.on("end", () => console.log(JSON.parse(body)));
});<?php
$ch = curl_init("https://api.providerdownload.com/v1/providers/deactivated?limit=50");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
print_r(json_decode(curl_exec($ch), true));
curl_close($ch);var json = await client.GetStringAsync(
"https://api.providerdownload.com/v1/providers/deactivated?limit=50");
Console.WriteLine(json);Returns your current plan, monthly usage, and remaining quota. When you reach 80% of your limit, a quota_warning message is included.
curl https://api.providerdownload.com/account/me \ -H "X-Api-Key: YOUR_API_KEY"
r = requests.get(
"https://api.providerdownload.com/account/me",
headers={"X-Api-Key": "YOUR_API_KEY"}
)
account = r.json()
print(f"Plan: {account['plan']}")
print(f"Used: {account['monthly_used']} / {account['monthly_limit']}")
if account.get("quota_warning"):
print(f"Warning: {account['quota_warning']}")const res = await fetch("https://api.providerdownload.com/account/me", {
headers: { "X-Api-Key": "YOUR_API_KEY" }
});
const account = await res.json();
console.log(`Plan: ${account.plan}`);
console.log(`Used: ${account.monthly_used} / ${account.monthly_limit}`);
if (account.quota_warning) console.warn(account.quota_warning);https.get({
hostname: "api.providerdownload.com",
path: "/account/me",
headers: { "X-Api-Key": "YOUR_API_KEY" }
}, res => {
let body = "";
res.on("data", c => body += c);
res.on("end", () => {
const account = JSON.parse(body);
console.log(`Plan: ${account.plan}, Used: ${account.monthly_used}`);
});
});<?php
$ch = curl_init("https://api.providerdownload.com/account/me");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: YOUR_API_KEY"]);
$account = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Plan: " . $account["plan"] . "\n";
echo "Used: " . $account["monthly_used"] . " / " . $account["monthly_limit"] . "\n";var json = await client.GetStringAsync(
"https://api.providerdownload.com/account/me");
Console.WriteLine(json);If you've lost your API key, POST your email address and we'll rotate it and send the new key to your inbox. Always returns the same response regardless of whether the email exists, to prevent account enumeration.
curl -X POST https://api.providerdownload.com/auth/resend-key \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'import requests
r = requests.post("https://api.providerdownload.com/auth/resend-key",
json={"email": "you@example.com"})
print(r.json()["message"])await fetch("https://api.providerdownload.com/auth/resend-key", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "you@example.com" }),
});Response
{
"message": "If that email has an account, a new API key has been sent."
}Immediately deactivates your current key and returns a new one. Use this if your key is compromised. Requires your existing key.
curl -X POST https://api.providerdownload.com/account/rotate-key \ -H "X-Api-Key: your_api_key"
import requests
r = requests.post("https://api.providerdownload.com/account/rotate-key",
headers={"X-Api-Key": "your_api_key"})
new_key = r.json()["api_key"]
print(new_key)const res = await fetch("https://api.providerdownload.com/account/rotate-key", {
method: "POST",
headers: { "X-Api-Key": "your_api_key" },
});
const { api_key } = await res.json();
console.log("New key:", api_key);Response
{
"api_key": "nppes_...",
"plan": "free",
"message": "Your previous key has been deactivated. Store this new key safely."
}Returns a one-time URL to the Stripe Customer Portal where paid subscribers can cancel, upgrade, downgrade, or update their payment method. Requires an active Starter or Pro API key.
curl -X POST https://api.providerdownload.com/account/portal \ -H "X-Api-Key: your_api_key"
import requests, webbrowser
r = requests.post("https://api.providerdownload.com/account/portal",
headers={"X-Api-Key": "your_api_key"})
webbrowser.open(r.json()["url"])const res = await fetch("https://api.providerdownload.com/account/portal", {
method: "POST",
headers: { "X-Api-Key": "your_api_key" },
});
const { url } = await res.json();
window.location.href = url;Response
{
"url": "https://billing.stripe.com/session/..."
}providerdownload.com. Cancellations take effect at the end of the current billing period.Webhooks let you receive a POST to your server when NPI data changes. Available on the Pro plan only.
| Event | Description |
|---|---|
npi.deactivated | NPI was deactivated in this data update |
npi.name_changed | Provider acquired a new legal name |
npi.reactivated | Previously deactivated NPI was reactivated |
npi.address_changed | Practice address (line1, line2, city, state, or ZIP) changed |
npi.taxonomy_changed | Primary taxonomy code changed, or the set of non-primary codes changed (order-independent) |
All three filter fields are optional and OR'd together. An NPI fires if it matches any filter. Omit all filters to receive changes for all NPIs.
| Field | Type | Description |
|---|---|---|
filter_npis | string[] | List of 10-digit NPI numbers |
filter_states | string[] | 2-letter state codes (e.g. ["FL","TX"]) |
filter_zips | string[] | ZIP codes (e.g. ["32301","32303"]) |
Every request includes an X-Webhook-Signature header with a sha256= HMAC signature computed over the raw request body using your subscription secret. Always verify this before processing.
import hashlib, hmac
def verify(secret: str, body: bytes, signature: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)const crypto = require("crypto");
function verify(secret, body, signature) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}<?php
function verifyWebhook(string $secret, string $body, string $signature): bool {
$expected = "sha256=" . hash_hmac("sha256", $body, $secret);
return hash_equals($expected, $signature);
}using System.Security.Cryptography;
using System.Text;
bool Verify(string secret, byte[] body, string signature) {
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = "sha256=" + Convert.ToHexString(hmac.ComputeHash(body)).ToLower();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(hash),
Encoding.UTF8.GetBytes(signature));
}{
"event": "npi.deactivated",
"period": "2026-06",
"sent_at": "2026-06-03T14:22:00Z",
"npis": [
{
"npi": "1234567890",
"last_name": "Smith",
"first_name": "John",
"organization_name": null,
"state": "FL",
"city": "Miami",
"zip": "33101"
}
]
}Create a webhook. The secret in the response is shown once — store it immediately.
# Subscribe to all deactivations in Florida
curl -X POST https://api.providerdownload.com/v1/webhooks/subscriptions \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/nppes",
"events": ["npi.deactivated", "npi.name_changed"],
"filter_states": ["FL"]
}'
# Subscribe to specific NPIs
curl -X POST https://api.providerdownload.com/v1/webhooks/subscriptions \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/nppes",
"events": ["npi.deactivated"],
"filter_npis": ["1234567890", "9876543210"]
}'
# Subscribe to Tallahassee by ZIP code
curl -X POST https://api.providerdownload.com/v1/webhooks/subscriptions \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/nppes",
"events": ["npi.deactivated", "npi.name_changed"],
"filter_zips": ["32301","32303","32304","32308","32309","32312"]
}'import requests
r = requests.post(
"https://api.providerdownload.com/v1/webhooks/subscriptions",
headers={"X-Api-Key": "YOUR_API_KEY"},
json={
"url": "https://your-server.com/webhooks/nppes",
"events": ["npi.deactivated", "npi.name_changed"],
"filter_states": ["FL"],
}
)
sub = r.json()
print("ID:", sub["id"])
print("Secret:", sub["secret"]) # store this — shown onceconst res = await fetch("https://api.providerdownload.com/v1/webhooks/subscriptions", {
method: "POST",
headers: { "X-Api-Key": "YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://your-server.com/webhooks/nppes",
events: ["npi.deactivated", "npi.name_changed"],
filter_states: ["FL"],
})
});
const sub = await res.json();
console.log("Secret:", sub.secret); // store this — shown onceList your active webhook subscriptions. The secret is not returned here.
Returns the last 100 delivery attempts for a subscription — status, HTTP response code, attempt count, and timestamp.
Delete a webhook subscription.