Skip to main content

Webhook Integration

Throne can send real-time notifications to your server whenever a gift or contribution is received on a creator's wishlist.

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 2xx status quickly (Throne has a 10-second timeout)

  • Verify the signature before acting on the payload

Request format

Property

Value

Method

POST

Content-Type

application/json; charset=utf-8

Timeout

10 seconds

Headers

Header

Description

X-Signature-Timestamp

Unix timestamp in seconds (decimal string, e.g. "1700000000")

X-Signature-Ed25519

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

"1"

Contract version

event_id

string

Unique delivery ID (UUID)

event_type

string

One of: gift_purchased, contribution_purchased, gift_crowdfunded

data

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

  1. Read T from the X-Signature-Timestamp header.

  2. Read B as the raw request body (do not re-encode a parsed object).

  3. Decode X-Signature-Ed25519 from hex to 64 bytes β†’ S.

  4. Build the signed message: M = T + "." + B

  5. Verify the Ed25519 signature S over the UTF-8 bytes of M using the public key above.

Additional checks:

  • Reject requests where T is missing or non-numeric.

  • Reject requests where S does not decode to exactly 64 bytes.

  • If you have already processed event_id, return 2xx without 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_id

  • Do 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.

Did this answer your question?