Skip to content

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.

import "github.com/dnsid-ai/dnsid-go/httpsig"
profile := httpsig.NewFromIdentityManagerKeyProvider(idm, httpsig.Config{})

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{})

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.

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)

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.

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.

Full signing and verification surfaces per language: Go httpsig · TypeScript http-signatures · Python HTTP signatures.