> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fingerprint.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Web Bot Auth Implementation

> Web Bot Auth is used as a verification method for verified bots and signed agents.

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 ↗](https://datatracker.ietf.org/doc/html/draft-meunier-http-message-signatures-directory) allowing the agent to share their public keys, and a [protocol draft ↗](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture) 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 ↗](https://ed25519.cr.yp.to/) private key to sign your requests. This example uses the [OpenSSL ↗](https://openssl-library.org/) `genpkey` command:

```bash theme={"theme":"github-dark-dimmed"}
# 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**

```bash theme={"theme":"github-dark-dimmed"}
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 ↗`](https://github.com/jphastings/jwker) command line application.

```bash theme={"theme":"github-dark-dimmed"}
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.

<Accordion title="JSON web key (jwk) file example">
  ```json theme={"theme":"github-dark-dimmed"}
  {"kty":"OKP","crv":"Ed25519","x":"HzO8jiKnwNPaQZDnTzQN5_GrHwdFb8IqT-Z-HTxus44"}
  ```
</Accordion>

**Compute the JWK thumbprint and add it as a `kid` attribute**

Now you need to generate the [base64 URL-encoded JWK thumbprint ↗](https://www.rfc-editor.org/rfc/rfc8037.html#appendix-A.3) from the public key.

<Accordion title="JWK thumbprint code example and result">
  ```javascript theme={"theme":"github-dark-dimmed"}
  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:

  ```json theme={"theme":"github-dark-dimmed"}
  {"kty":"OKP","crv":"Ed25519","x":"HzO8jiKnwNPaQZDnTzQN5_GrHwdFb8IqT-Z-HTxus44",
  "kid":"KYG4GtgVVPWw3r1AXgM6fYFw5kAvRZiJVxZY1Rsgc3Q"}
  ```
</Accordion>

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 ↗](https://datatracker.ietf.org/doc/html/draft-meunier-http-message-signatures-directory-05).

<Steps>
  <Step title="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.
  </Step>

  <Step title="Serve the directory over HTTPS">
    **This URL should serve a standard JWKS**

    Besides `x`, `crv`, `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 `kty`, `crv`, `kid`, and `x`created above.

    <Accordion title="Hosted JWKS example">
      ```bash theme={"theme":"github-dark-dimmed"}
      $ 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"}]}
      ```
    </Accordion>
  </Step>

  <Step title="Ensure correct media type">
    Ensure the directory response has the `Content-Type` HTTP header value of `application/http-message-signatures-directory+json`.

    <Accordion title="How to verify the content-type header">
      ```bash theme={"theme":"github-dark-dimmed"}
      $ 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

      ```
    </Accordion>
  </Step>
</Steps>

# Register Your Agent

You can submit a request to register your agent using this form in the Fingerprint Dashboard: [Submit Your Bot](https://dashboard.fingerprint.com/submit-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 ↗](https://datatracker.ietf.org/doc/html/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 ↗](https://www.rfc-editor.org/rfc/rfc9421#name-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:

| Name                          | Value                                                                |
| ----------------------------- | -------------------------------------------------------------------- |
| AI agent public key directory | `https://ai-agent.fyi/.well-known/http-message-signatures-directory` |
| `signature-agent` component   | `"https://ai-agent.fyi"`                                             |
| AI agent making requests to   | `https://example.com/blog/2029-12-31-introduction`                   |
| `@authority` component        | `"example.com"`                                                      |

<Note>
  **Use components with only ASCII values.**

  Fingerprint currently does not support `bs` or `sf` parameter designed to serialize non-ASCII values into ASCII equivalents.
</Note>

## 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 ↗](https://www.rfc-editor.org/rfc/rfc8941.html)
* The message includes a structured field [label](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture-05#section-5.7.1\$0) (e.g. **sig1**="[https://ai-agent.fyi](https://ai-agent.fyi)"), as labeling is not supported.

### `Signature-Input` header

Construct a [`Signature-Input header ↗`](https://www.rfc-editor.org/rfc/rfc9421#name-the-signature-input-http-fi) over your chosen components. The header must meet the following requirements.

| **Required component parameter** | **Requirement**                                                                                                                                                                                                                                  |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `tag`                            | web-bot-auth                                                                                                                                                                                                                                     |
| `keyid`                          | This should be equal to the `kid` value of the public key<br />(notice the difference in naming, `keyid` vs `kid`)                                                                                                                               |
| `alg`                            | ed25519                                                                                                                                                                                                                                          |
| `created`                        | This should be equal to a Unix timestamp associated with when the message was sent by your agent                                                                                                                                                 |
| `expires`                        | This should be equal to a Unix timestamp associated with when Fingerprint should no longer attempt to verify the message. <br />A short `expires` reduces the likelihood of replay attacks, and Fingerprint recommends using a one hour duration |
| `nonce`                          | Strongly recommended: a random value that must not be reused, at least 16 bytes, base64 encoded                                                                                                                                                  |

### `Signature` header

Construct a [`Signature header ↗`](https://www.rfc-editor.org/rfc/rfc9421#name-the-signature-http-field) over your chosen components by signing them with your private key and converting to base64.

<Accordion title="JavaScript example of header generation">
  ```javascript theme={"theme":"github-dark-dimmed"}
  /**
  * 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')}:`,
    };
  }
  ```
</Accordion>

### 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 ↗](https://playwright.dev/docs/api/class-route) functionality of Playwright.

An example request header collection may look like this:

```bash theme={"theme":"github-dark-dimmed"}
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==:
```

<Info>
  **All requests**, made by your agent, must be signed, including static resource (images, javascript) and [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) requests. If using Playwright to add custom headers is not an option, an alternative way to do it is via a browser extension.
</Info>

If you have any questions, please reach out to **`bot-registration@fingerprint.com`**

## Helpful Links

* [**Example of a public key directory ↗**](https://chatgpt.com/.well-known/http-message-signatures-directory)

## Next Steps

<Columns cols={3}>
  <Card title="Test your Web Bot Auth implementation ↗" icon="braces" horizontal href="https://fingerprint.com/web-bot-auth/test">
    Using Fingerprint's Web Bot Auth Verifier (WBAV) app to test if your agent adheres to the standard
  </Card>

  <Card title="Submit your agent to Fingerprint directory" icon="shield" horizontal href="https://dashboard.fingerprint.com/submit-bot">
    Submit your agent or bot to the Fingerprint Bot Directory
  </Card>

  <Card title="Automation Intelligence API reference" icon="code" horizontal href="/reference/automation-intelligence-api">
    Send requests to the Collect Intelligence endpoint and read the response fields.
  </Card>
</Columns>
