When TengineAI calls your API on behalf of a tool, it can sign or authenticate the outbound request so your server can verify the request came from TengineAI — and not an arbitrary caller.
Three strategies are available, configured per tool via auth_strategy.
| Strategy | auth_strategy | Body integrity | Replay protection | Use when |
|---|---|---|---|---|
| None | none | ❌ | ❌ | Public endpoints; network-layer trust (VPN, IP allowlist) |
| Static Bearer | static_bearer | ❌ | ❌ | Third-party webhooks; simple token-gated APIs |
| Request Signing | hmac_signature | ✅ | ✅ | Your own APIs — recommended default |
No authentication headers are added. The request is sent as-is.
Use only for public endpoints or when the network layer (VPN, IP allowlist) is the trust boundary.
TengineAI adds a standard Authorization: Bearer <secret> header to every outbound request.
{
"auth_strategy": "static_bearer",
"encrypted_auth_secret": "your-api-token"
}
The secret is stored encrypted at rest and injected at execution time:
Authorization: Bearer <your-api-token>
Does not prevent replay. A captured request can be replayed indefinitely — the token never changes. To mitigate: check
X-Tengine-Timestamp(reject requests older than ±5 minutes) and deduplicate onX-Tengine-Request-Id. Rotate the token regularly.
TengineAI computes an HMAC-SHA256 signature over a canonical string derived from the request and adds it as a header. Your server verifies the signature using the same shared secret.
{
"auth_strategy": "hmac_signature",
"encrypted_auth_secret": "your-shared-secret"
}
This provides:
| Header | Value |
|---|---|
X-Tengine-Signature | tng2=<hex-encoded HMAC-SHA256> |
X-Tengine-Timestamp | Unix timestamp (seconds) |
X-Tengine-Request-Id | UUID — unique per request |
These are added alongside the standard identity headers (X-Tengine-Project-Id, X-Tengine-Member-Id, etc.).
TengineAI signs the following canonical string — a single space-delimited line:
tng2 <timestamp> <request_id> <METHOD> <host> <path_and_query> <body_sha256> <project_id> <member_id>
| Component | Description |
|---|---|
tng2 | Fixed version prefix |
<timestamp> | Value of X-Tengine-Timestamp (Unix seconds, string) |
<request_id> | Value of X-Tengine-Request-Id (UUID string) |
<METHOD> | HTTP method, uppercased (GET, POST, etc.) |
<host> | Host + port from the request URL (e.g. api.example.com) |
<path_and_query> | Path and query string (e.g. /v1/users/42?env=prod) |
<body_sha256> | SHA256 hex digest of the JSON body (keys sorted, compact); empty string if no body |
<project_id> | TengineAI project ID (string); empty string if unavailable |
<member_id> | Member external ID from session; empty string if no member context |
Body serialization:
json.dumps(body, sort_keys=True, separators=(",", ":"))
Signature computation: HMAC-SHA256, hex-encoded:
hmac.new(secret.encode("utf-8"), canonical_string.encode("utf-8"), hashlib.sha256).hexdigest()
Complete Python middleware you can drop into your API:
import hashlib
import hmac
import json
import time
from typing import Optional
SHARED_SECRET = "your-shared-secret"
MAX_TIMESTAMP_DRIFT_SECONDS = 300 # Reject requests older than 5 minutes
def verify_tengine_signature(
*,
method: str,
host: str,
path_and_query: str,
body: Optional[dict],
x_tengine_signature: str,
x_tengine_timestamp: str,
x_tengine_request_id: str = "",
x_tengine_project_id: str = "",
x_tengine_member_id: str = "",
) -> bool:
# 1. Reject stale requests
try:
request_time = int(x_tengine_timestamp)
except ValueError:
return False
if abs(time.time() - request_time) > MAX_TIMESTAMP_DRIFT_SECONDS:
return False
# 2. Compute body SHA256
if body:
body_json = json.dumps(body, sort_keys=True, separators=(",", ":"))
body_sha256 = hashlib.sha256(body_json.encode("utf-8")).hexdigest()
else:
body_sha256 = ""
# 3. Build canonical string
canonical = (
f"tng2 {x_tengine_timestamp} {x_tengine_request_id} {method.upper()} "
f"{host} {path_and_query} {body_sha256} {x_tengine_project_id} {x_tengine_member_id}"
)
# 4. Compute expected signature
expected = hmac.new(
SHARED_SECRET.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
# 5. Parse received signature (format: "tng2=<hex>")
if not x_tengine_signature.startswith("tng2="):
return False
received = x_tengine_signature[len("tng2="):]
# 6. Constant-time comparison to prevent timing attacks
return hmac.compare_digest(expected, received)
FastAPI usage:
from fastapi import FastAPI, Header, Request, HTTPException
app = FastAPI()
@app.post("/v1/contacts/lookup")
async def handle_tool_call(
request: Request,
x_tengine_signature: str = Header(...),
x_tengine_timestamp: str = Header(...),
x_tengine_request_id: str = Header(default=""),
x_tengine_project_id: str = Header(default=""),
x_tengine_member_id: str = Header(default=""),
):
body = await request.json()
parsed = request.url
valid = verify_tengine_signature(
method=request.method,
host=parsed.netloc,
path_and_query=parsed.path + (f"?{parsed.query}" if parsed.query else ""),
body=body,
x_tengine_signature=x_tengine_signature,
x_tengine_timestamp=x_tengine_timestamp,
x_tengine_request_id=x_tengine_request_id,
x_tengine_project_id=x_tengine_project_id,
x_tengine_member_id=x_tengine_member_id,
)
if not valid:
raise HTTPException(status_code=401, detail="Invalid signature")
# x_tengine_member_id identifies which user TengineAI is acting on behalf of.
# It is attribution from the session context — not an authentication credential.
return {"status": "ok"}
Wrong body serialization — The body must be serialized with sort_keys=True and compact separators (",", ":") before hashing. Any difference in serialization produces a different hash.
Missing path_and_query — If the URL has a query string, it must be included: /v1/users?active=true, not just /v1/users.
Missing request_id in canonical string — tng2 includes X-Tengine-Request-Id in the signed payload. Omitting it will cause every verification to fail.
Comparing with == — Always use hmac.compare_digest() to prevent timing attacks.
Not checking the timestamp — Without a drift window check, valid signatures can be replayed indefinitely.
Every tool call includes X-Tengine-Project-Id, X-Tengine-Member-Id, and related headers. These are attribution — they tell your API which project and user the model is acting on behalf of, derived from the authenticated TengineAI session.
They are not authentication credentials. Do not use X-Tengine-Member-Id as a trust signal. Verify the request using the signature or bearer token first, then use the identity headers for business logic (e.g. "look up records for this member").
X-Tengine-Member-Id is established