Hugo | MCP API
Give Feedback

Request Signing

Updated on April 28, 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=...)
X-Crisp-Session-Id Crisp conversation session ID, when the request is tied to a conversation

The signature is computed from the exact payload below:

{timestamp}.{website_id}.{raw_request_body}

The digest is the lowercase hexadecimal HMAC-SHA256 of this payload, prefixed with sha256=.

Hugo adds X-Crisp-Website-Id and X-Crisp-Timestamp to every MCP request. X-Crisp-Signature is added whenever the integration has a signing secret, which is the default for MCP integrations. If your MCP server receives unsigned requests, regenerate the signing secret in the integration settings.

X-Crisp-Session-Id is added when Hugo calls your MCP server in the context of a Crisp conversation. This is the Crisp session ID for the originating conversation. You can use it to correlate tool calls with that conversation, fetch conversation data with the Crisp REST API, or write audit logs. It is not included in the signature payload, and it may be absent from requests that are not tied to a conversation, such as connection checks or health checks.


Getting Your Signing Secret

The signing secret is a dedicated secret generated for each MCP integration.

When you create an MCP integration, Hugo generates a secret with an mcp_sec_ prefix. You can rotate it from the integration settings:

  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.