Quick answer: Throne can send an HTTPS POST to your endpoint the moment a gift, contribution, or crowdfunded gift comes in. Every request is signed so you can verify it actually came from Throne.
How it works
When you enable the Webhook integration and configure 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 responsibility: Respond with a 2xx status within 10 seconds.
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 this 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",
"price": 1099,
"currency": "USD",
"is_surprise_gift": true
}price is in the smallest currency unit × 100 (e.g. 1099 = $10.99). currency is an ISO 4217 code (e.g. "USD", "EUR"). gifter_username is "Anonymous" when the sender chose to remain anonymous. message is omitted when there is no gift message.
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",
"price": 1099,
"currency": "USD",
"is_surprise_gift": true
}gifter_username is "Anonymous" when the sender chose to remain anonymous. message is omitted when there is no gift message. price is in the smallest currency unit × 100 (e.g. 1099 = $10.99). currency is an ISO 4217 code (e.g. "USD", "EUR").
Verifying signatures
All requests are signed so 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.
