How it works
When a creator enables the Webhook integration in Throne and configures a subscriber URL, Throne sends an HTTPS POST request to that URL each time a qualifying event occurs. Every request includes a cryptographic signature so you can confirm it came from Throne.
Your responsibilities:
Expose a POST endpoint over HTTPS
Respond with a
2xxstatus quickly (Throne has a 10-second timeout)Verify the signature before acting on the payload
Request format
Property | Value |
Method |
|
Content-Type |
|
Timeout | 10 seconds |
Headers
Header | Description |
| Unix timestamp in seconds (decimal string, e.g. |
| Hex-encoded Ed25519 signature (128 hex characters = 64 bytes) |
Payload structure
Every request body is a JSON object with the following envelope:
Field | Type | Description |
|
| Contract version |
| string | Unique delivery ID (UUID) |
| string | One of: |
| object | Event-specific fields (see below) |
gift_purchased
{
"creator_id": "string",
"creator_username": "string",
"gifter_username": "string",
"message": "string (optional)",
"item_name": "string",
"item_thumbnail_url": "string",
"is_surprise_gift": true
}
contribution_purchased
{
"creator_id": "string",
"creator_username": "string",
"gifter_username": "string",
"message": "string (optional)",
"item_name": "string",
"item_thumbnail_url": "string",
"amount": 1099,
"currency": "USD"
}amount is in the smallest currency unit Γ 100 (e.g. 1099 = $10.99). currency is an ISO 4217 code (e.g. "USD", "EUR").
gift_crowdfunded
{
"creator_id": "string",
"creator_username": "string",
"item_name": "string",
"item_thumbnail_url": "string",
"is_surprise_gift": true
}gifter_username is "Anonymous" when the sender chose to remain anonymous. message is omitted when there is no gift message.
Verifying signatures
All requests are signed so that you can validate that the request came from Throne
Throne's public key (Ed25519, PEM)
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAPXbUfxh7XL4SYUVcfhmYMIbxvtR9E9LDd8gPJ1PwSD8=
-----END PUBLIC KEY-----
How to verify
Read
Tfrom theX-Signature-Timestampheader.Read
Bas the raw request body (do not re-encode a parsed object).Decode
X-Signature-Ed25519from hex to 64 bytes βS.Build the signed message:
M = T + "." + BVerify the Ed25519 signature
Sover the UTF-8 bytes ofMusing the public key above.
Additional checks:
Reject requests where
Tis missing or non-numeric.Reject requests where
Sdoes not decode to exactly 64 bytes.If you have already processed
event_id, return2xxwithout repeating side effects (idempotency).
Example (Node.js)
import { createVerify } from "crypto";
function verifyThroneWebhook(rawBody, timestamp, signatureHex) {
const message = `${timestamp}.${rawBody}`;
const verify = createVerify("Ed25519");
verify.update(message);
return verify.verify(THRONE_PUBLIC_KEY_PEM, Buffer.from(signatureHex, "hex"));
}Security checklist
Subscriber URLs must use HTTPS in production
Always verify the signature before any business logic
Validate the timestamp to limit replay attacks
Deduplicate on
event_idDo not log the full signature β a short prefix is enough for debugging
Need help?
Contact us through the Throne support portal if you run into issues setting up your integration.
