# Webhooks
To simulate and verify webhook integration, use the following test endpoints. These endpoints allow you to validate your webhook handling logic, including signature verification, without waiting for real events. Try them via our API Reference (opens new window).
# General
OriginStamp supports two types of webhooks, which you can configure via your API Key Settings. Webhooks are a critical part of your integration, notifying your systems when timestamping or proof-related events occur.
# Proof Webhook
A proof webhook is sent after you request a Proof. This webhook delivers the proof data for your previously submitted hash.
- A proof webhook URL must be set in the API key settings.
- The webhook is triggered shortly (typically < 1 minute) after proof generation.
- The payload contains all the necessary Merkle data to verify the inclusion of your hash in the anchored tree.
# Tree Webhook
You will receive a tree webhook when the root hash of a Merkle Tree has been successfully anchored on the blockchain. This is essential for the Timestamp Workflow.
- A tree webhook URL must be set in your API key settings.
- Once anchored, all hashes submitted during the last aggregation period and included in that tree are considered verified.
- The webhook payload includes metadata about the tree and blockchain anchoring.
# Delivery & Retry Logic
Webhooks are delivered via POST
requests to the configured URLs.
Make sure your server is publicly accessible and capable of handling incoming POST requests.
If your endpoint fails to respond with a 200
OK, we will retry up to 3 times.
- Each attempt has a 10-second timeout.
- Always return a
200
OK response as soon as you have accepted the request.
# Signature Verification
Every webhook request includes an x-signature
header containing an HMAC-SHA256 signature
of the request body. You can verify this signature using your API key to ensure payload integrity
and authenticity.
The signature is generated by:
HMAC_SHA256(secret, canonical_json_body)
Canonical JSON
To ensure consistent signing across languages, the JSON body must:
- Have all keys sorted lexicographically.
- Have no added whitespace (i.e. compact form).
Example: {"b": 1, "a": 2}
must be aligned to {"a":1,"b":2}
# Python
import json
import hmac
import hashlib
def calculate_signature(payload: dict, secret: str) -> str:
# Sort keys and remove whitespace to ensure deterministic string.
payload_str = json.dumps(payload, separators=(',', ':'), sort_keys=True)
signature = hmac.new(
key=secret.encode("utf-8"),
msg=payload_str.encode("utf-8"),
digestmod=hashlib.sha256
).hexdigest()
return signature
# Java
public class WebhookSigner {
public static String calculateSignature(ObjectNode payload, String secret) throws Exception {
// Use Jackson to serialize with sorted keys and no pretty print
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
// Canonical JSON: sorted keys and compact format
String jsonString = mapper.writeValueAsString(payload);
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(keySpec);
byte[] hmacBytes = hmacSha256.doFinal(jsonString.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : hmacBytes) {
hexString.append(String.format("%02x", b));
}
return hexString.toString();
}
}
# TypeScript / Node.js
import * as crypto from 'crypto';
function canonicalizeJson(obj: any): string {
if (obj === null || typeof obj !== 'object') return JSON.stringify(obj);
if (Array.isArray(obj)) return `[${obj.map(canonicalizeJson).join(',')}]`;
const sortedKeys = Object.keys(obj).sort();
const entries = sortedKeys.map(key => `"${key}":${canonicalizeJson(obj[key])}`);
return `{${entries.join(',')}}`;
}
function calculateSignature(payload: any, secret: string): string {
const canonicalJson = canonicalizeJson(payload);
return crypto
.createHmac('sha256', secret)
.update(canonicalJson, 'utf8')
.digest('hex');
}
# Example
Given the API secret: non-valid-api-key
With the canonical JSON for the following payload:
{
"treeId": "3f9474cd-a8b1-418e-bcad-88233049fe92",
"dateCreated": 1754328093419,
"currencyId": 0,
"currency": "BTC",
"submitStatus": 3,
"timestamp": 1710000000000,
"rootSha256": "3fd4bc2b4f14b9798c23a50d09b61741f8594b8bb2bb4842f5e2b25797c06dbc",
"transaction": "0x3f289856c20c0471fb335db48d4df28718ff2c005b3cf7f80231ba52649b853f"
}
The resulting signature would be:
188f5a41b0d3f011b038dca26f6ca6ef3b3e1a886337f8683601017a6b531625
# Testing
# Tree Verification Test
This simulates a Merkle Tree verification event. The payload is static and does not change. It can be used to test your webhook handling logic for tree verification events.
POST /v1/webhook/tree
Request:
{
"target": "https://yourdomain.com/webhooks/tree"
}
# Proof Delivery Test
This simulates a complete timestamp proof delivery, including Merkle branch data. The payload is static and does not change. It can be used to test your webhook handling logic for tree verification events.
POST /v1/webhook/proof
Request:
{
"target": "https://yourdomain.com/webhooks/proof"
}
# Security Notes
- An
x-api-key
header is also required for all test requests. - Use HTTPS for all webhook targets to protect against MITM attacks.
- All webhook requests are signed using HMAC-SHA256 via the
x-signature
header. - Webhook targets should verify the signature before trusting the payload.
Abuse of this testing service (e.g., targeting third-party services) may result in it being rate-limited or disabled without prior notice.