curl --request POST \
--url https://api.openagent.to/api/v1/webhook/sync \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--header 'x-nonce: <api-key>' \
--header 'x-signature: <api-key>' \
--data '
{
"event_type": "SYSTEM.PING",
"timestamp": "2026-09-01T09:15:32.104Z"
}
'import requests
url = "https://api.openagent.to/api/v1/webhook/sync"
payload = {
"event_type": "SYSTEM.PING",
"timestamp": "2026-09-01T09:15:32.104Z"
}
headers = {
"x-api-key": "<api-key>",
"x-nonce": "<api-key>",
"x-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-nonce': '<api-key>',
'x-signature': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({event_type: 'SYSTEM.PING', timestamp: '2026-09-01T09:15:32.104Z'})
};
fetch('https://api.openagent.to/api/v1/webhook/sync', 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.openagent.to/api/v1/webhook/sync",
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([
'event_type' => 'SYSTEM.PING',
'timestamp' => '2026-09-01T09:15:32.104Z'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>",
"x-nonce: <api-key>",
"x-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.openagent.to/api/v1/webhook/sync"
payload := strings.NewReader("{\n \"event_type\": \"SYSTEM.PING\",\n \"timestamp\": \"2026-09-01T09:15:32.104Z\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("x-nonce", "<api-key>")
req.Header.Add("x-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.openagent.to/api/v1/webhook/sync")
.header("x-api-key", "<api-key>")
.header("x-nonce", "<api-key>")
.header("x-signature", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"event_type\": \"SYSTEM.PING\",\n \"timestamp\": \"2026-09-01T09:15:32.104Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.openagent.to/api/v1/webhook/sync")
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-nonce"] = '<api-key>'
request["x-signature"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"event_type\": \"SYSTEM.PING\",\n \"timestamp\": \"2026-09-01T09:15:32.104Z\"\n}"
response = http.request(request)
puts response.read_body{
"success": true
}{
"error": {
"code": "Validation error",
"details": [
{
"field": "email",
"message": "Invalid email format",
"allowedValues": [
"<string>"
]
}
]
},
"message": "Validation error",
"meta": {
"timestamp": "2026-09-01T09:15:32.104Z",
"requestId": "3f1c9d2e-6b7a-4f18-9c53-0a2b6d4e8f10"
},
"statusCode": 422,
"success": false
}{
"error": {
"code": "Validation error",
"details": [
{
"field": "email",
"message": "Invalid email format",
"allowedValues": [
"<string>"
]
}
]
},
"message": "Validation error",
"meta": {
"timestamp": "2026-09-01T09:15:32.104Z",
"requestId": "3f1c9d2e-6b7a-4f18-9c53-0a2b6d4e8f10"
},
"statusCode": 422,
"success": false
}{
"error": {
"code": "Validation error",
"details": [
{
"field": "email",
"message": "Invalid email format",
"allowedValues": [
"<string>"
]
}
]
},
"message": "Validation error",
"meta": {
"timestamp": "2026-09-01T09:15:32.104Z",
"requestId": "3f1c9d2e-6b7a-4f18-9c53-0a2b6d4e8f10"
},
"statusCode": 422,
"success": false
}{
"error": {
"code": "Validation error",
"details": [
{
"field": "email",
"message": "Invalid email format",
"allowedValues": [
"<string>"
]
}
]
},
"message": "Validation error",
"meta": {
"timestamp": "2026-09-01T09:15:32.104Z",
"requestId": "3f1c9d2e-6b7a-4f18-9c53-0a2b6d4e8f10"
},
"statusCode": 422,
"success": false
}Receive an issuer platform event
Inbound webhook the issuer platform calls to mirror an issuer, its securities and its investors into the transfer agent. SYSTEM.PING is acknowledged and does nothing else. ONBOARDING.CREATED upserts the issuer, each security and each investor by the ids the platform sends, and stores the externalApiIntegration credentials if present — so the platform re-sending the same payload is safe and expected (it syncs every ten minutes), provided each send carries a fresh nonce and signature.
Authentication is three headers, all required. x-api-key names an active key. x-nonce is the signing time as unix milliseconds, accepted within five minutes of server time and never twice for the same key. x-signature is the lowercase hex HMAC-SHA256 of <x-nonce>.<raw body> keyed with the API secret, where the body is the exact bytes sent — sign the serialized string you transmit, not the object. Every authentication failure (unknown or disabled key, stale or reused nonce, signature mismatch, key bound to a different issuer) is the same 401 so the response does not say which check failed. Nothing is written until the signature verifies.
An unclaimed key is bound to data.issuer._id on its first correctly signed call and rejects every other issuer from then on. The body is validated only after authentication, so a malformed payload from an unauthenticated caller is a 401, not a 422.
The success body is exactly { "success": true } — not the standard envelope.
curl --request POST \
--url https://api.openagent.to/api/v1/webhook/sync \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--header 'x-nonce: <api-key>' \
--header 'x-signature: <api-key>' \
--data '
{
"event_type": "SYSTEM.PING",
"timestamp": "2026-09-01T09:15:32.104Z"
}
'import requests
url = "https://api.openagent.to/api/v1/webhook/sync"
payload = {
"event_type": "SYSTEM.PING",
"timestamp": "2026-09-01T09:15:32.104Z"
}
headers = {
"x-api-key": "<api-key>",
"x-nonce": "<api-key>",
"x-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-nonce': '<api-key>',
'x-signature': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({event_type: 'SYSTEM.PING', timestamp: '2026-09-01T09:15:32.104Z'})
};
fetch('https://api.openagent.to/api/v1/webhook/sync', 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.openagent.to/api/v1/webhook/sync",
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([
'event_type' => 'SYSTEM.PING',
'timestamp' => '2026-09-01T09:15:32.104Z'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>",
"x-nonce: <api-key>",
"x-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.openagent.to/api/v1/webhook/sync"
payload := strings.NewReader("{\n \"event_type\": \"SYSTEM.PING\",\n \"timestamp\": \"2026-09-01T09:15:32.104Z\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("x-nonce", "<api-key>")
req.Header.Add("x-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.openagent.to/api/v1/webhook/sync")
.header("x-api-key", "<api-key>")
.header("x-nonce", "<api-key>")
.header("x-signature", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"event_type\": \"SYSTEM.PING\",\n \"timestamp\": \"2026-09-01T09:15:32.104Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.openagent.to/api/v1/webhook/sync")
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-nonce"] = '<api-key>'
request["x-signature"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"event_type\": \"SYSTEM.PING\",\n \"timestamp\": \"2026-09-01T09:15:32.104Z\"\n}"
response = http.request(request)
puts response.read_body{
"success": true
}{
"error": {
"code": "Validation error",
"details": [
{
"field": "email",
"message": "Invalid email format",
"allowedValues": [
"<string>"
]
}
]
},
"message": "Validation error",
"meta": {
"timestamp": "2026-09-01T09:15:32.104Z",
"requestId": "3f1c9d2e-6b7a-4f18-9c53-0a2b6d4e8f10"
},
"statusCode": 422,
"success": false
}{
"error": {
"code": "Validation error",
"details": [
{
"field": "email",
"message": "Invalid email format",
"allowedValues": [
"<string>"
]
}
]
},
"message": "Validation error",
"meta": {
"timestamp": "2026-09-01T09:15:32.104Z",
"requestId": "3f1c9d2e-6b7a-4f18-9c53-0a2b6d4e8f10"
},
"statusCode": 422,
"success": false
}{
"error": {
"code": "Validation error",
"details": [
{
"field": "email",
"message": "Invalid email format",
"allowedValues": [
"<string>"
]
}
]
},
"message": "Validation error",
"meta": {
"timestamp": "2026-09-01T09:15:32.104Z",
"requestId": "3f1c9d2e-6b7a-4f18-9c53-0a2b6d4e8f10"
},
"statusCode": 422,
"success": false
}{
"error": {
"code": "Validation error",
"details": [
{
"field": "email",
"message": "Invalid email format",
"allowedValues": [
"<string>"
]
}
]
},
"message": "Validation error",
"meta": {
"timestamp": "2026-09-01T09:15:32.104Z",
"requestId": "3f1c9d2e-6b7a-4f18-9c53-0a2b6d4e8f10"
},
"statusCode": 422,
"success": false
}Authorizations
Issuer API key identifying the caller. The key must be active. It must belong to the issuer named in data.issuer._id, or be unclaimed — the first correctly signed call binds an unclaimed key to that issuer permanently.
Unix time in milliseconds when the request was signed, as a decimal string. Rejected if it is more than five minutes from server time in either direction, and rejected if this key has already used it — every request needs a fresh value, including retries.
Lowercase hex HMAC-SHA256, keyed with the API secret, over the string <x-nonce>.<raw request body> — the nonce, a literal dot, then the exact bytes sent as the body. Compared in constant time.
Body
Response
Event accepted and applied
Non-standard body: the webhook acknowledges with only success.
true

