Hugo | MCP API
Give Feedback

Request Signing

Updated on March 30, 2026

Request signing allows you to cryptographically verify that requests to your MCP server originate from Crisp.

Overview

Hugo signs every request using HMAC-SHA256. Each request includes these headers:

Header Description
X-Crisp-Website-Id Your Crisp website identifier
X-Crisp-Timestamp Unix timestamp of the request
X-Crisp-Signature HMAC-SHA256 signature (sha256=...)

The signature is computed from: {timestamp}.{website_id}.{request_body}


Getting Your Signing Secret

  1. Navigate to Hugo > Integrations in your Crisp dashboard
  2. Select your MCP integration
  3. In Request Signing, click Regenerate
  4. Copy the secret immediately — it won't be shown again
Warning: Regenerating invalidates the previous secret.

Verifying Signatures

Add signature verification to your MCP server using the official SDK pattern:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { createHmac, timingSafeEqual } from "crypto";
import http from "http";

const SIGNING_SECRET = process.env.CRISP_SIGNING_SECRET!;
const MAX_AGE_SECONDS = 300;

function verifySignature(
  headers: http.IncomingHttpHeaders,
  body: string
): boolean {
  const signature = headers["x-crisp-signature"] as string;
  const timestamp = headers["x-crisp-timestamp"] as string;
  const websiteId = headers["x-crisp-website-id"] as string;

  if (!signature || !timestamp || !websiteId) {
    return false;
  }

  // Replay protection
  const age = Math.abs(Date.now() / 1000 - parseInt(timestamp, 10));
  if (age > MAX_AGE_SECONDS) {
    return false;
  }

  // Compute expected signature
  const payload = `${timestamp}.${websiteId}.${body}`;
  const expected =
    "sha256=" +
    createHmac("sha256", SIGNING_SECRET).update(payload).digest("hex");

  // Timing-safe comparison
  if (signature.length !== expected.length) {
    return false;
  }

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

// Create MCP server
const server = new McpServer({
  name: "my-mcp-server",
  version: "1.0.0",
});

// Register your tools
server.tool("my_tool", "Description", {}, async () => {
  return { content: [{ type: "text", text: "Result" }] };
});

// Create HTTP server with signature verification
const httpServer = http.createServer(async (req, res) => {
  // Read body
  const chunks: Buffer[] = [];
  for await (const chunk of req) {
    chunks.push(chunk);
  }
  const body = Buffer.concat(chunks).toString();

  // Verify signature
  if (!verifySignature(req.headers, body)) {
    res.writeHead(401, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ error: "Invalid signature" }));
    return;
  }

  // Handle MCP request
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => crypto.randomUUID(),
  });

  await server.connect(transport);
  await transport.handleRequest(req, res, JSON.parse(body));
});

httpServer.listen(3000);
Important: Verify the signature against the raw request body before parsing JSON.