How to authenticate OpenAI Operator requests using HTTP message signatures
Cloudflare recently introduced a new authentication standard, HTTP message signatures, designed to securely verify automated traffic from known bot operators. OpenAI has adopted this standard in its OpenAI Operator product, which allows ChatGPT agents to perform actions on behalf of users.
This new approach replaces the traditional method of IP-based allowlisting with a cryptographic scheme based on signed HTTP requests. Instead of maintaining and rotating lists of IP addresses, websites can now verify a bot’s identity using public keys published by the bot provider.
For developers and security teams, this simplifies bot verification while improving security guarantees. It’s especially relevant in the context of AI agents, which may trigger critical workflows like submitting forms, triggering transactions, or modifying content.
Understanding the bot authentication standard
Under this standard, each bot operator exposes a public key via a well-known endpoint:
/.well-known/http-message-signatures-directory
This endpoint returns metadata used to verify signed requests. For example, OpenAI’s key directory is available at https://chatgpt.com/.well-known/http-message-signatures-directory
.
A sample response looks like this:
{
"keys": [
{
"kid": "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
"crv": "Ed25519",
"kty": "OKP",
"x": "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
"use": "sig",
"nbf": 1735689600,
"exp": 1754656053
}
],
"signature_agent": "<https://chatgpt.com>",
"purpose": "ai"
}
This data allows any server receiving a request from OpenAI’s agent to verify the signature using the published public key. Keys are versioned using the kid
(key ID) field and have defined validity periods (nbf
, exp
).
Publishing keys at a predictable location removes the need for out-of-band key distribution and supports dynamic key rotation with minimal coordination.
Example: verifying a signed request from OpenAI Operator
To demonstrate how this works in practice, we instructed OpenAI’s agent to visit a page that includes Castle instrumentation. As expected, our server later received a POST request to the following endpointhttps://deviceandbrowserinfo.com/info_device
The request includes the following relevant headers:
{
"Signature": "sig1=:OxtIqCmhbdHV9oNRn2rCmSaMJFC83CrzTsgC6CaULImrbish5S1h5PwO+8tSNGG8GzSHpRl/E/mgy5vKvuD0DQ==:",
"Signature-Input": "sig1=(\\"@authority\\" \\"@method\\" \\"@path\\" \\"signature-agent\\");created=1754053298;keyid=\\"otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg\\";expires=1754056898;nonce=\\"...\\";tag=\\"web-bot-auth\\";alg=\\"ed25519\\"",
"Signature-Agent": "\\"<https://chatgpt.com>\\"",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 ..."
}
These headers indicate the request was signed using the Ed25519 key with ID otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg
, which matches the key published by OpenAI. The Signature-Agent
header asserts the identity of the sender, and the signature covers the HTTP method, path, authority, and agent fields.
To verify the request, we’ll use Cloudflare’s web-bot-auth
Node.js library, which provides helpers for signature parsing and verification.
Step-by-step: verifying signatures in Node.js
The following Node.js snippet shows how to verify a signed request from OpenAI using Cloudflare’s web-bot-auth
package. Note that the signature is time-sensitive. If you're replaying a real request, verification may fail if the signature has expired.
import { verify } from "web-bot-auth";
import { verifierFromJWK } from "web-bot-auth/crypto";
(async () => {
// Public key retrieved from OpenAI's /.well-known/http-message-signatures-directory
const OPEN_AI_KEY = {
kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
crv: "Ed25519",
kty: "OKP",
x: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
use: "sig",
nbf: 1735689600,
exp: 1754656053
};
// Construct a synthetic Request object for the demo
const signedRequest = new Request("<https://deviceandbrowserinfo.com/info_device>", {
method: "POST",
headers: {
Signature: "sig1=:OxtIqCmhbdHV9oNRn2rCmSaMJFC83CrzTsgC6CaULImrbish5S1h5PwO+8tSNGG8GzSHpRl/E/mgy5vKvuD0DQ==:",
"Signature-Input": 'sig1=("@authority" "@method" "@path" "signature-agent");created=1754053298;keyid="otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg";expires=1754056898;nonce="...";tag="web-bot-auth";alg="ed25519"',
"Signature-Agent": "\\"<https://chatgpt.com>\\"",
"Content-Type": "application/json"
},
body: "{}"
});
try {
await verify(signedRequest, await verifierFromJWK(OPEN_AI_KEY));
console.log("Signature verification successful");
} catch (err) {
console.error("Signature verification failed:", err.message);
}
})();
In a production setup, you would extract the Request
object from your web framework (e.g. Express or Fastify) and dynamically fetch and cache keys from the /.well-known/http-message-signatures-directory
endpoint.
Why signed requests are a better way to verify AI agents
This example shows how to verify that a request was sent by an authenticated bot, using only its public key and HTTP headers. The benefits over traditional IP-based allowlists are clear:
- No infrastructure dependencies: You don’t need to maintain up-to-date list of IP ranges.
- Tamper resistance: Signatures are tied to the request content and timestamp, making them impossible to forge, contrary to authentication based on the user agent.
- Flexible deployment: Bot identity is verified at the application layer, even if requests are routed through proxies, CDNs, or serverless functions.
For bot providers like OpenAI, this allows them to publish a single cryptographic identity. For receiving sites, it enables fine-grained authentication tied to specific agents and actions.
While this example uses Node.js, Cloudflare’s GitHub repository includes implementations for Go (via Caddy) and Rust as well.
Technical considerations
Verifying bot requests using signed HTTP headers is a more secure and maintainable alternative to managing IP allowlists. It provides strong authenticity guarantees with minimal integration overhead.
While the example in this post is intentionally simple, deploying this approach in production requires a few additional considerations:
- Key caching and rotation: Public keys retrieved from the
/.well-known/http-message-signatures-directory
endpoint should be cached and refreshed based on theirnbf
(not before) andexp
(expiration) fields. This ensures reliability and supports future key rotation. - Multi-tenant support: If your service interacts with multiple agents or bot operators, you may need to dynamically discover or configure multiple key directories.
As more AI agents are entrusted with user actions, cryptographic authentication provides a scalable foundation for verifying their requests.