curl --request POST \
--url https://api.proofage.net/v1/webhook-subscriptions \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--header 'X-HMAC-Signature: <api-key>' \
--data '
{
"url": "<string>",
"include_document_data": false,
"statuses": []
}
'import requests
url = "https://api.proofage.net/v1/webhook-subscriptions"
payload = {
"url": "<string>",
"include_document_data": False,
"statuses": []
}
headers = {
"X-API-Key": "<api-key>",
"X-HMAC-Signature": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-API-Key': '<api-key>',
'X-HMAC-Signature': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({url: '<string>', include_document_data: false, statuses: []})
};
fetch('https://api.proofage.net/v1/webhook-subscriptions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.proofage.net/v1/webhook-subscriptions",
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' => '<string>',
'include_document_data' => false,
'statuses' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>",
"X-HMAC-Signature: <api-key>"
],
]);
$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.proofage.net/v1/webhook-subscriptions"
payload := strings.NewReader("{\n \"url\": \"<string>\",\n \"include_document_data\": false,\n \"statuses\": []\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("X-HMAC-Signature", "<api-key>")
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.proofage.net/v1/webhook-subscriptions")
.header("X-API-Key", "<api-key>")
.header("X-HMAC-Signature", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"<string>\",\n \"include_document_data\": false,\n \"statuses\": []\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.proofage.net/v1/webhook-subscriptions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["X-HMAC-Signature"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"<string>\",\n \"include_document_data\": false,\n \"statuses\": []\n}"
response = http.request(request)
puts response.read_body{
"id": "0199c4b2-7d1e-7a3f-9c0e-5b6a7c8d9e0f",
"url": "https://hooks.zapier.com/hooks/standard/12345678/abcdef/",
"statuses": [
"approved",
"declined"
],
"include_document_data": false,
"created_at": "2026-10-08T12:00:00+00:00"
}{
"error": {
"code": "WEBHOOK_SUBSCRIPTION_LIMIT",
"message": "A workspace can have at most 50 webhook subscriptions. Delete one first."
}
}Create a webhook subscription
Subscribes a URL to decision webhooks, in addition to the workspace webhook URL set in the console. Built for REST hooks such as Zapier: subscribe when an automation is turned on, delete the subscription when it is turned off. A workspace can have up to 50.
Each delivery has the workspace webhook’s body and headers, signed with the secret key that
signed this request while that key exists, and with the active secret key after it is deleted.
Unless include_document_data is true, the body leaves out document, fingerprint_signals and
manual_moderation.performed_by. A delivery answered with 410 Gone deletes the subscription.
curl --request POST \
--url https://api.proofage.net/v1/webhook-subscriptions \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--header 'X-HMAC-Signature: <api-key>' \
--data '
{
"url": "<string>",
"include_document_data": false,
"statuses": []
}
'import requests
url = "https://api.proofage.net/v1/webhook-subscriptions"
payload = {
"url": "<string>",
"include_document_data": False,
"statuses": []
}
headers = {
"X-API-Key": "<api-key>",
"X-HMAC-Signature": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-API-Key': '<api-key>',
'X-HMAC-Signature': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({url: '<string>', include_document_data: false, statuses: []})
};
fetch('https://api.proofage.net/v1/webhook-subscriptions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.proofage.net/v1/webhook-subscriptions",
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' => '<string>',
'include_document_data' => false,
'statuses' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>",
"X-HMAC-Signature: <api-key>"
],
]);
$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.proofage.net/v1/webhook-subscriptions"
payload := strings.NewReader("{\n \"url\": \"<string>\",\n \"include_document_data\": false,\n \"statuses\": []\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("X-HMAC-Signature", "<api-key>")
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.proofage.net/v1/webhook-subscriptions")
.header("X-API-Key", "<api-key>")
.header("X-HMAC-Signature", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"<string>\",\n \"include_document_data\": false,\n \"statuses\": []\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.proofage.net/v1/webhook-subscriptions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["X-HMAC-Signature"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"<string>\",\n \"include_document_data\": false,\n \"statuses\": []\n}"
response = http.request(request)
puts response.read_body{
"id": "0199c4b2-7d1e-7a3f-9c0e-5b6a7c8d9e0f",
"url": "https://hooks.zapier.com/hooks/standard/12345678/abcdef/",
"statuses": [
"approved",
"declined"
],
"include_document_data": false,
"created_at": "2026-10-08T12:00:00+00:00"
}{
"error": {
"code": "WEBHOOK_SUBSCRIPTION_LIMIT",
"message": "A workspace can have at most 50 webhook subscriptions. Delete one first."
}
}Authorizations
Your workspace public key (pk_test_… or pk_live_…).
HMAC-SHA256 of the request, hex-encoded, keyed with the workspace secret key. See API authentication.
Body
Where ProofAge POSTs the decision webhooks, signed like the workspace webhook. It must be a public URL: private, local and cloud-metadata addresses are refused.
2048Include the document read from the identity document (names, date of birth, document number), the fingerprint signals (IP address, timezones) and the name and email of the operator who moderated. Off by default, so personal data stays out of the subscriber's logs.
Only send these statuses: approved, declined, resubmission_requested, review, abandoned, expired. Omit it, or send null, for all of them.
approved, declined, resubmission_requested, abandoned, expired, review