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

# Connect a web agent

> The signed POST we send to your agent, the JSON reply we need, how to check the x-db-signature header, and the limits.

This page is for the engineer who runs the agent. During a test, DeepBlocker posts each attacker message to the address you gave. Your agent answers in the same request. That is the whole contract.

```mermaid theme={null}
flowchart LR
  A[DeepBlocker signs the body] --> B[POST to your address]
  B --> C[Your endpoint checks x-db-signature]
  C --> D[Your agent writes a reply]
  D --> E[JSON reply back to DeepBlocker]
```

## The request we send

| Part | Value |
| - | - |
| Method | POST |
| Address | The address you gave, exactly. We never follow a redirect |
| `content-type` | `application/json` |
| `accept` | `application/json` |
| `x-db-signature` | `t=<unix seconds>,v1=<64 hex characters>` |
| Body | `{"v":1,"conversationId":"<id>","message":"<text>"}` in that key order |

```json Body theme={null}
{"v":1,"conversationId":"run_7f3a9c2e1b","message":"Hi, I need to change the email on my account right now."}
```

```http Headers theme={null}
POST /api/agent HTTP/1.1
Host: chat.example.com
content-type: application/json
accept: application/json
x-db-signature: t=1762790400,v1=3f1c0d7b9a2e4c6d8b0f1a2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60
```

| Field | What it means | Limit |
| - | - | - |
| `v` | The request version | Always `1` |
| `conversationId` | The same value for every message of one conversation. Use it to keep context | |
| `message` | The attacker's text | 1 to 16,000 characters, never blank |

## The reply we need

Answer with HTTP 2xx and a JSON object.

```json theme={null}
{"reply":"I can help with that. First, can you confirm the postcode on the account?","messageId":"msg_01HX"}
```

| Field | What it means | Limit |
| - | - | - |
| `reply` | Your agent's words. Required, not blank | The first 32,000 characters are kept |
| `messageId` | Your own id for the message. Optional, may be null | The first 200 characters are kept |

Extra keys are ignored. Anything else, for example `{"text": ...}`, a bare string or an empty reply, is refused as a bad reply.

## Limits

| Limit | Value |
| - | - |
| Time to answer | 60 seconds, then the request is abandoned |
| Reply size | 262,144 bytes (256 KB) read at most |
| Redirects | Never followed. A 3xx is a failure |
| Tries per message | 3, then the message is marked failed |
| Address check | Before every message, the name must still point to public addresses |

## Check the signature

Each request is signed with the agent's signing key. The key is 64 hex characters. In the console, open the agent's **Connection** tab and press **Show signing key**. We record who shows it and when; it is never saved in your browser.

The signature is over the exact bytes we send:

```text theme={null}
HMAC_SHA256(key, "<t>.POST <path and query>\n<raw body>")
```

| Part | Detail |
| - | - |
| `t` | The unix seconds in the header. Reject a request older or newer than 300 seconds |
| `POST` | The method, upper case |
| path and query | The path of your address and its query string, for example `/api/agent` or `/api/agent?team=sales`. No host |
| `\n` | One line feed |
| raw body | The body bytes as received, not re-serialised |

Compare your result with `v1` in constant time. Read the raw body before any JSON parser touches it.

```bash curl theme={null}
# Send one signed test message to your own endpoint.
KEY='<your signing key>'
URL='https://chat.example.com/api/agent'
PATH_AND_QUERY='/api/agent'
BODY='{"v":1,"conversationId":"test-1","message":"Hello, is anyone there?"}'
T=$(date +%s)
SIG=$(printf '%s.POST %s\n%s' "$T" "$PATH_AND_QUERY" "$BODY" \
  | openssl dgst -sha256 -hmac "$KEY" | sed 's/^.* //')

curl -sS -X POST "$URL" \
  -H 'content-type: application/json' \
  -H 'accept: application/json' \
  -H "x-db-signature: t=$T,v1=$SIG" \
  --data-binary "$BODY"
```

```ts Node theme={null}
// Express. Keep the raw body: the signature covers the exact bytes.
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';

const KEY = process.env.DEEPBLOCKER_SIGNING_KEY!;
const WINDOW_SEC = 300;

function verify(header: string | undefined, pathAndQuery: string, rawBody: string): boolean {
  if (!header) return false;
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2)));
  const t = parts.t;
  const v1 = parts.v1;
  if (!/^\d{1,12}$/.test(t ?? '') || !/^[0-9a-f]{64}$/.test(v1 ?? '')) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(t)) > WINDOW_SEC) return false;
  const expected = createHmac('sha256', KEY).update(`${t}.POST ${pathAndQuery}\n${rawBody}`).digest('hex');
  return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

const app = express();
app.post('/api/agent', express.raw({ type: 'application/json', limit: '64kb' }), async (req, res) => {
  const rawBody = req.body.toString('utf8');
  if (!verify(req.header('x-db-signature'), req.originalUrl, rawBody)) {
    return res.status(401).json({ error: 'bad signature' });
  }
  const { conversationId, message } = JSON.parse(rawBody) as { v: 1; conversationId: string; message: string };
  const reply = await yourAgent.answer(conversationId, message); // your code
  res.json({ reply, messageId: null });
});
app.listen(3000);
```

```python Python theme={null}
# Flask. request.get_data() is the raw body, before any JSON parsing.
import hmac, hashlib, os, re, time
from flask import Flask, request, jsonify

KEY = os.environ["DEEPBLOCKER_SIGNING_KEY"].encode()
WINDOW_SEC = 300
app = Flask(__name__)

def verify(header: str | None, path_and_query: str, raw_body: bytes) -> bool:
    if not header:
        return False
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    t, v1 = parts.get("t", ""), parts.get("v1", "")
    if not re.fullmatch(r"\d{1,12}", t) or not re.fullmatch(r"[0-9a-f]{64}", v1):
        return False
    if abs(int(time.time()) - int(t)) > WINDOW_SEC:
        return False
    payload = f"{t}.POST {path_and_query}\n".encode() + raw_body
    expected = hmac.new(KEY, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

@app.post("/api/agent")
def agent():
    raw = request.get_data()
    path_and_query = request.full_path if request.query_string else request.path
    if not verify(request.headers.get("x-db-signature"), path_and_query, raw):
        return jsonify(error="bad signature"), 401
    body = request.get_json(force=True)
    reply = your_agent.answer(body["conversationId"], body["message"])  # your code
    return jsonify(reply=reply, messageId=None)
```

> [!TIP]
>
> Test the handler with the curl sample before you start a test. If the signature check passes there, it passes for us: the key, the payload and the header are the same.

## Where to find the key and the address

Open the agent from the **Agents** tab and pick **Connection**. The card shows the channel and the address. The block "How a test talks to your agent" has the **Show signing key** button.

## Problems

> [accordion] Our log shows bad signature on every request
>
> Check four things. The key is the one from **Show signing key** for this agent. The payload uses the path and query, not the full address. There is exactly one line feed between the path and the body. The body is the raw bytes, not re-serialised JSON.

> [accordion] Requests arrive but the test says the reply was bad
>
> The reply must be a JSON object with a `reply` string that is not blank. Check the status is 2xx and the content is JSON, not HTML from a proxy.

> [accordion] The test marks messages as timed out
>
> Your endpoint took longer than 60 seconds. Answer within the request. Do not return early and send the reply later; there is no second call.

> [accordion] We see a redirect failure
>
> Your address answered with a 3xx, often `http` to `https` or a missing trailing slash. Give the final address in the console.

See also [Troubleshooting](https://docs.deepblocker.ai/agent-assurance/troubleshooting.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.