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
- Navigate to Hugo > Integrations in your Crisp dashboard
- Select your MCP integration
- In Request Signing, click Regenerate
- 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.