Sign HTTP requests
HTTP Message Signatures (RFC 9421) let an agent prove its identity on every request — no token exchange, no issuer in the loop. The agent signs the request with its operational key; the receiver reads the signer’s domain from the signature’s keyid (domain#kid), verifies the domain like any DNSid verification, and checks the signature against the agent’s published JWKS. Verification also requires a trusted lifecycle-log reader; no account or central service is needed.
Use this profile for DNSid-aware counterparties talking directly to each other. (Authenticate as an agent has the overview.)
All snippets assume an identity manager constructed as shown in the SDK overview. To verify inbound requests, configure trust for the signer’s log on that manager.
Construct the profile
Section titled “Construct the profile”import "github.com/dnsid-ai/dnsid-go/httpsig"
profile := httpsig.NewFromIdentityManagerKeyProvider(idm, httpsig.Config{})import { HttpSignaturesProfile } from '@dnsid-ai/http-signatures';
const profile = HttpSignaturesProfile.fromIdentityManager(idm);from dnsid import HttpSignatureProfile
profile = HttpSignatureProfile.from_identity_manager(manager)Sign outbound requests
Section titled “Sign outbound requests”The simplest integration wraps your HTTP client so every request is signed automatically:
// Wrap a client: every outbound request is signed before dispatch.client := profile.CreateSignedHTTPClient(http.DefaultClient, httpsig.SigningOptions{})resp, err := client.Post("https://peer.example/orders", "application/json", body)
// Or sign a single request:signed, err := profile.CreateSignedHTTPRequest(req, httpsig.SigningOptions{})// Wrap fetch: every outbound request is signed before it is sent.const signedFetch = profile.createSignedFetch({ fetch });const resp = await signedFetch('https://peer.example/orders', { method: 'POST', body: JSON.stringify(order),});
// Or sign a single Request (returns a signed copy):const signed = await profile.createSignedHttpRequest(req);import httpx
# Wrap an httpx.Client: every outbound request is signed before sending.client = profile.create_signed_http_client(httpx.Client())resp = client.post("https://peer.example/orders", json=order)
# Async services: profile.create_signed_async_http_client(...) returns an# auto-signing httpx.AsyncClient.
# Or sign a single dnsid HttpRequest in place:signed = profile.create_signed_http_request(req)What gets signed: @method, @authority, and @target-uri are covered by default; when the request has a body, a SHA-256 Content-Digest header is added and covered too. The signature carries keyid (domain#kid), a creation timestamp, and a fresh nonce. Signing options let you cover additional components or set an expiry — see the per-language references below.
Verify inbound requests
Section titled “Verify inbound requests”Verification returns the signer’s VerifiedDomain — the same object domain verification produces — so your handler learns which accountable identity sent the request, not just that a signature was valid:
signer, err := profile.VerifyHTTPRequest(ctx, req)if err != nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return}log.Printf("request from %s (governed by %s)", signer.Domain(), signer.Record().GovernanceID)try { const signer = await profile.verifySignedHttpRequest(req); console.log(`request from ${signer.domain} (governed by ${signer.record.gi})`);} catch (err) { return new Response('unauthorized', { status: 401 });}from dnsid import HttpRequest, VerificationError
# Build a dnsid HttpRequest from your framework's inbound request# (Flask/FastAPI/ASGI request objects are not passed directly):req = HttpRequest(method=method, url=url, headers=headers, body=body)
try: signer = profile.verify_signed_http_request(req) print(f"request from {signer.domain} (governed by {signer.record.gi})")except VerificationError: return unauthorized()The verifier checks the covered components, signature freshness (creation time, expiry, clock skew), Content-Digest consistency when a body is present, that the signing key appears in the signer’s published JWKS, and the signature itself — and, through the identity resolution step, everything domain verification always checks (record signature, status, lifecycle log). Failures raise the same typed verification errors as VerifyDomain, so revoked or stale identities are rejected, not just bad signatures.
Related profile: Web Bot Auth
Section titled “Related profile: Web Bot Auth”The SDKs also ship a Web Bot Auth profile — the same signature mechanics specialized for crawlers and bots identifying themselves to origin servers. See the per-language references: Go · TypeScript · Python.
API reference
Section titled “API reference”Full signing and verification surfaces per language: Go httpsig · TypeScript http-signatures · Python HTTP signatures.