Skip to main content
This guide explains how to implement Web Bot Auth signing in your AI agent and register it with Fingerprint. Registering your bot has several advantages: it becomes an authorized bot and Fingerprint will not flag it as malicious. You can also decide how your bot is represented by Fingerprint APIs, by picking the desired bot name, category and provider.

Web Bot Auth

Web Bot Auth is an authentication method that leverages cryptographic signatures in HTTP messages to verify that a request comes from an authorized AI Agent. It relies on two active IETF drafts: a directory draft ↗ allowing the agent to share their public keys, and a protocol draft ↗ defining how these keys should be used to attach agent’s identity to HTTP requests. This documentation goes over specific integration within Fingerprint.

Generate a valid signing key

You need to generate a signing key which will be used to authenticate your agent’s requests. Generate a unique Ed25519 ↗ private key to sign your requests. This example uses the OpenSSL ↗ genpkey command:
# make sure you're using the latest OpenSSL, not an older LibreSSL
# older versions of LibreSSL don't support Ed25519
# you can install the latest version with homebrew
openssl genpkey -algorithm ed25519 -out private-key.pem
Needless to say, keep your private-key.pem file secret. Extract your public key
openssl pkey -in private-key.pem -pubout -out public-key.pem
Convert the public key to JSON Web Key (JWK) using a tool of your choice. This example uses jwker ↗ command line application.
go install github.com/jphastings/jwker/cmd/jwker@latest
jwker public-key.pem public-key.jwk
By following these steps, you have generated a private key and a public key, then converted the public key to a JSON web key (jwk) format.
{"kty":"OKP","crv":"Ed25519","x":"HzO8jiKnwNPaQZDnTzQN5_GrHwdFb8IqT-Z-HTxus44"}
Compute the JWK thumbprint and add it as a kid attribute Now you need to generate the base64 URL-encoded JWK thumbprint ↗ from the public key.
const crypto = require('crypto');
const fs = require('fs');
// reading the public key in JWK format we created in the previoius step
const jwk = JSON.parse(fs.readFileSync('public-key.jwk'));

// taking only the required attributes in sorted order (canonicalization)
const required = Object.fromEntries(
  ['crv', 'kty', 'x'].map(k => [k, jwk[k]])
);

// calculating the thumbprint
const kid = crypto
  .createHash('sha256')
  .update(JSON.stringify(required))
  .digest('base64url');

// adding the thumbprint hash as a new `kid` attribute (key ID)
fs.writeFileSync('public-key.jwk', JSON.stringify({ ...jwk, kid }));

The resulting file should look like this:
{"kty":"OKP","crv":"Ed25519","x":"HzO8jiKnwNPaQZDnTzQN5_GrHwdFb8IqT-Z-HTxus44",
"kid":"KYG4GtgVVPWw3r1AXgM6fYFw5kAvRZiJVxZY1Rsgc3Q"}
At this stage you should have your public key in JWK format with the kid attribute required for Web Bot Auth, now it’s time to host it publicly.

Host a key directory

You need to host a key directory which creates a way for your agent to authenticate its requests to Fingerprint. This directory should follow the definition from the active IETF draft draft-meunier-http-message-signatures-directory-05 ↗.
1

Host a key directory

Host a key directory at https://ai-agent.fyi/.well-known/http-message-signatures-directory (note that the full path is a requirement). This key directory should serve a JSON Web Key Set (JWKS) with the public key derived from your private signing key.
2

Serve the directoory over HTTPS

This URL should serve a standard JWKSBesides xcrv, kid, and kty, you can include other standard JSON Web Key parameters. Multiple Ed25519 keys are supported. Only those for which you provide a signature in the above format are going to be used. Fingerprint will ignore all other key types and key parameters except those containing ktycrv, kid, and xcreated above.
$ curl https://ai-agent.fyi/.well-known/http-message-signatures-directory
{"keys": [{"kty":"OKP","crv":"Ed25519","x":"HzO8jiKnwNPaQZDnTzQN5_GrHwdFb8IqT-Z-HTxus44","kid":"KYG4GtgVVPWw3r1AXgM6fYFw5kAvRZiJVxZY1Rsgc3Q","use":"sig"}]}
3

Ensure correct media type

Ensure the directory response has the Content-Type HTTP header value of application/http-message-signatures-directory+json.
$ curl -si https://ai-agent.fyi/.well-known/http-message-signatures-directory \
  | grep -i -E "content-type:"
# expected output
content-type: application/http-message-signatures-directory+json

Register Your Agent

You can submit a request to register your agent using this form in the Fingerprint Dashboard: Submit Your Bot. After submitting all the required information, please wait 1-2 weeks for the agent registration. You’ll get a confirmation email when we have successfully registered your agent.

Sign Your Requests

After a successful agent registration, start signing the requests. The signature protocol is defined in draft-meunier-web-bot-auth-architecture-05 ↗. Fundamentally, signing the requests means adding three mandatory HTTP signature headers to each request that your agent makes: Signature, Signature-Input, and Signature-Agent.

Choose a set of components to sign

A component is either an HTTP header, or any derived components ↗ in the HTTP Message Signatures specification. Fingerprint recommends the following: Choose at least the @authority and signature-agent components. You can optionally add more, such as HTTP method or another HTTP header. The @authority represents the host you are sending requests to. The signature-agent is the root URI of the agent’s public key directory. Consider this example:
NameValue
AI agent public key directoryhttps://ai-agent.fyi/.well-known/http-message-signatures-directory
signature-agent component"https://ai-agent.fyi"
AI agent making requests tohttps://example.com/blog/2029-12-31-introduction
@authority component"example.com"
Use components with only ASCII values.Fingerprint currently does not support bs or sf parameter designed to serialize non-ASCII values into ASCII equivalents.

Construct the required headers

Construct the three required headers for Web Bot Auth.

Signature-Agent header

Construct a Signature-Agent header ↗ that points to your key directory root URI. Note that Fingerprint will fail to verify a message if:
  • The message includes a Signature-Agent header that is not an https://
  • The Signature-Agent is not a bare domain (paths, query params are not allowed)
  • The message includes a valid URI but does not enclose it in double quotes. This is due to Signature-Agent being a structured field ↗
  • The message includes a structured field label (e.g. sig1=“https://ai-agent.fyi”), as labeling is not supported.

Signature-Input header

Construct a Signature-Input header ↗ over your chosen components. The header must meet the following requirements.
Required component parameterRequirement
tagweb-bot-auth
keyidThis should be equal to the kid value of the public key
(notice the difference in naming, keyid vs kid)
alged25519
createdThis should be equal to a Unix timestamp associated with when the message was sent by your agent
expiresThis should be equal to a Unix timestamp associated with when Fingerprint should no longer attempt to verify the message.
A short expires reduces the likelihood of replay attacks, and Fingerprint recommends using a one hour duration
nonceStrongly recommended: a random value that must not be reused, at least 16 bytes, base64 encoded

Signature header

Construct a Signature header ↗ over your chosen components by signing them with your private key and converting to base64.
/**
* Build and sign the HTTP message signature for a given URL.
* Signed components: @authority, signature-agent
* @param {string} url - The full URL of the request being signed
* @returns {{ 'signature-agent': string, 'signature-input': string, 'signature': string }}
*/
async function getSignatureHeaders(url) {
  const signatureAgentUri = 'https://ai-agent.fyi';
  const { hostname } = new URL(url);
  
  const created = Math.floor(Date.now() / 1000);
  // expires = now + 1 hour
  const expires = created + 60*60;
  
  // Component values
  const authorityValue = hostname;
  
  const nonce = crypto.randomBytes(16).toString('base64');
  
  // Signature Input string (what gets signed)
  // Format per RFC 9421 Section 2.1:
  //   "<component-name>": <value>\n  (one per component)
  //   "@signature-params": <sig-params>
  const signatureParams =
  `("@authority" "signature-agent")` +
  `;created=${created}` +
  `;expires=${expires}` +
  `;keyid="${KID}"` + // KID is the JWK thumbprint from the public key
  `;alg="ed25519"` +
  `;nonce="${nonce}"` +
  `;tag="web-bot-auth"`;
  
  // Signature base string per RFC 9421 Section 2.5
  // Each component line: "<name>": <value>
  // Header values are quoted strings; @authority is a plain token (hostname)
  const signingInput = [
  `"@authority": ${authorityValue}`,
  `"signature-agent": "${signatureAgentUri}"`,
  `"@signature-params": ${signatureParams}`,
  ].join('\n');
  
  // Sign with Ed25519 private key
  const signature = crypto.sign(null, Buffer.from(signingInput), PRIVATE_KEY_PEM);
  
  return {
  'signature-agent': `"${signatureAgentUri}"`,
  'signature-input': `sig1=${sigParams}`,
  'signature': `sig1=:${signature.toString('base64')}:`,
  };
}

Add the headers to your agent’s requests

Attach these three headers to your agent’s requests. Note that all requests need to be signed, including requests that make fetch requests or download static resources. One option to attach custom headers to all requests is by using Page.route ↗ functionality of Playwright. An example request header collection may look like this:
Signature-Agent: "https://ai-agent.fyi"
Signature-Input: sig1=("@authority" "signature-agent")
 ;created=1735689600;expires=1735693200
 ;keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U"
 ;alg="ed25519"
 ;nonce="3q2+7oBVKDJKNGHs1A9pYQ=="
 ;tag="web-bot-auth"
Signature: sig1=:jdq0SqOwHdyHr9+r5jw3iYZH6aNGKijYp/EstF4RQTQdi5N5YYKrD+mCT1HA1nZDsi6nJKuHxUi/5Syp3rLWBA==:
All requests, made by your agent, must be signed, including static resource (images, javascript) and Fetch requests. If using Playwright to add custom headers is not an option, an alternative way to do it is via a browser extension.
If you have any questions, please reach out to bot-registration@fingerprint.com

Next Steps

Test your Web Bot Auth implementation ↗

Using Fingerprint’s Web Bot Auth Verifier (WBAV) app to test if your agent adheres to the standard

Submit your agent to Fingerprint directory

Subimt your agent or bot to the Fingerprint Bot Directory