Skip to content

Verifying Webhooks - API Reference

Your webhook URL is a public endpoint. Anyone who discovers it can POST to it, and a payload that merely looks like ours is trivial to construct. Every webhook we send is therefore signed, so you can confirm that a request came from us and was not modified in transit.

Verification is optional, but recommended for anything that acts on the payload — placing a trade, sending a notification, writing to a database. Verify before you process.

How it works

Verification is based on four things:

  • A shared secret, known only to you and SocialData
  • A timestamp, so old requests can be rejected
  • A unique event ID for each request
  • An HMAC-SHA256 signature over the raw request body

The flow:

  1. We build the JSON payload.
  2. We compute a signature from your webhook secret, the event ID and the timestamp.
  3. We send the payload with the signature in the request headers.
  4. Your server recomputes the signature from the raw body it received.
  5. If the two match, the request is authentic.

Request headers

Every webhook request carries these three headers:

HeaderDescription
X-TimestampUnix timestamp (seconds) at which the webhook was generated
X-Event-IdA unique UUID for this request
X-SignatureHMAC signature, prefixed with the scheme version

Example:

X-Timestamp: 1736944496
X-Event-Id: 49cd1f14-8325-4b44-9a5b-cffe7ffa82f8
X-Signature: v1=c104d8b39bbe76b201dfd7b0ef3606c57b053f38e4e678756e9da1fbbf7404f4

Signature scheme

  • Algorithm — HMAC-SHA256
  • Key — your webhook secret
  • Signing string{X-Event-Id}.{X-Timestamp}.{raw request body}

The digest is hex-encoded and prefixed with v1=. The prefix is part of the header value, not part of the digest — strip it before comparing, or include it on both sides.

Rejecting replays

A valid signature proves a request came from us. It does not prove it is new — a captured request stays valid forever unless you check. Two additional steps:

  • Check the timestamp. Reject anything outside a small tolerance of your current clock. Five minutes is a reasonable default.
  • Track event IDs. Record X-Event-Id for each request you accept, and reject one you have already processed.

Tracking event IDs also gives you idempotency for free, which is worth having regardless of security.

Code examples

Each example returns true only for a request that is authentic, recent and correctly signed. All three use a constant-time comparison — ===, == and strcmp leak timing information that can be used to recover a valid signature byte by byte.

import crypto from 'crypto';
// Express: mount with express.raw({ type: 'application/json' }) so that
// req.body is a Buffer of the original bytes rather than a parsed object.
function verifyWebhook(req, secret, toleranceSec = 300) {
const timestamp = req.headers['x-timestamp'];
const eventId = req.headers['x-event-id'];
const provided = req.headers['x-signature'];
if (!timestamp || !eventId || !provided) return false;
const match = /^v1=([0-9a-f]{64})$/.exec(provided);
if (!match) return false;
// Reject stale requests before doing any crypto work
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (!Number.isFinite(age) || age > toleranceSec) return false;
const rawBody = req.body.toString('utf8');
const signingString = `${eventId}.${timestamp}.${rawBody}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signingString, 'utf8')
.digest('hex');
// Both are 64 hex chars by construction, so the lengths always match
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(match[1], 'hex')
);
}

Setting your secret

You choose your own secret and send it to us — see Set webhook secret. Generate it with a CSPRNG rather than typing one, for example openssl rand -hex 32.

Deliveries are signed with the new value as soon as the change takes effect. Because the secret is used for signing rather than exchanged per request, rotating it briefly invalidates in-flight deliveries — rotate at a quiet moment, or accept both the old and new secret for a few minutes.

Before you integrate