Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.usetitan.app/llms.txt

Use this file to discover all available pages before exploring further.

Every request Titan sends to your webhook endpoint includes an X-Webhook-Signature header. You should verify this signature before processing any payload to confirm the request came from Titan and that its body was not tampered with in transit. The signature is an HMAC-SHA256 digest of the raw request body, computed using the hmacKey you set when creating the webhook. The header value takes the form:
X-Webhook-Signature: sha256=a3f1e2d4c5b6...
Always verify the signature before acting on a webhook payload. Skipping this step means any server on the internet could forge events and trigger actions in your application.

How verification works

  1. Read the raw request body bytes before parsing JSON. Do not use the serialized form of a parsed object — even minor whitespace differences will invalidate the signature.
  2. Compute HMAC-SHA256(rawBody, hmacKey) and hex-encode the result.
  3. Prepend sha256= to form the expected signature string.
  4. Compare your computed value to the X-Webhook-Signature header using a constant-time comparison to prevent timing attacks.

Using the official SDKs

The Titan SDKs handle signature verification for you.
import { verifyWebhookSignature, isEvent } from '@titan-api/sdk';

// In your Express/Fastify/etc. handler:
const event = await verifyWebhookSignature(rawBody, signatureHeader, secret);

if (isEvent(event, 'message.received')) {
  console.log('Message from:', event.payload.from.phoneNumber);
}

Manual verification

If you are not using an SDK, you can verify signatures with standard cryptography libraries in any language.
import crypto from 'crypto';

function verifySignature(rawBody: string, signature: string, secret: string): boolean {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Full handler example

The example below shows how to wire up signature verification in a complete Express handler.
import express from 'express';
import crypto from 'crypto';

const app = express();

// Use express.raw() to access the raw body bytes before parsing
app.post('/webhooks/titan', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const secret = process.env.TITAN_WEBHOOK_SECRET!;

  if (!verifySignature(req.body, signature, secret)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString());

  switch (event.event) {
    case 'message.received':
      handleMessage(event.payload);
      break;
    case 'session.status':
      handleSessionStatus(event.payload);
      break;
    // ... other event types
  }

  res.sendStatus(200);
});

function verifySignature(rawBody: Buffer, signature: string, secret: string): boolean {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

Best practices

Read the body bytes before any JSON parsing or serialization. JSON parsers may reorder keys or change whitespace. The signature is computed over the exact bytes Titan sent, so any transformation will cause verification to fail.
Use crypto.timingSafeEqual (Node.js), hmac.compare_digest (Python), hmac.Equal (Go), or hash_equals (PHP) rather than a regular string equality check. A simple === comparison can leak the signature length through timing differences, making a timing attack feasible.
Update your webhook’s hmacKey by calling PUT /api/webhooks/{id} with a new hmacKey value. Update your verification code to accept both the old and new keys during the transition window, then drop the old key once all in-flight deliveries have been processed.
If the X-Webhook-Signature header is absent, reject the request immediately with a 401 or 400 response. Titan always includes this header on legitimate deliveries.
The hmacKey is write-only: Titan never returns its value in API responses after creation. Store it securely in an environment variable or secrets manager when you register the webhook.