Skip to main content
When AUTH__ENABLED=true, every call to a Bindu agent has to do two things at once:
  1. Prove you’re allowed — attach a short-lived bearer token from Hydra.
  2. Prove you’re really you — sign the request body with your DID’s private key and attach the signature.
Either one missing and the agent rejects the call. Get both right and the request goes through. The Authentication overview explains the bearer-token side conceptually. The DID page explains the signing side. This page is the shortest path to a working request that satisfies both.

The four headers

Every call to an auth-on Bindu agent carries these four headers:
The agent verifies them in four gates. The first failure stops the chain.
Gate 4 collapses two sub-causes (clock skew and bad signature) into one invalid_signature reason. If you get this error, re-signing with a fresh timestamp eliminates clock-skew and replay as the cause.

I just want it to work — use a built-in caller

Most teams should not hand-roll this. Three callers in the Bindu repo do the whole chain for you: Quick smoke test against an auth-on agent, using the inbox:
ok:true, status:200 back means every gate above passed. You’re done. The rest of this page is for people writing the caller from scratch in a new language.

Hand-rolling it: one-time setup

You need three durable artifacts:
  • A seed — your 32-byte secret. Generates the Ed25519 keypair.
  • A DID — your public name, deterministically derived from the seed.
  • An OAuth client in Hydra — registered against the DID, with the public key in metadata.
1

Generate seed, DID, public key

Save all three.
The seed is your private key. Lose it and your DID is orphaned. Leak it and anyone can impersonate you.
2

Register the OAuth client in Hydra

The DID is the client_id. The public key goes in metadata.public_key — that’s how the agent finds your key during signature verification.
Save CLIENT_SECRET. You need it to mint tokens.

Hand-rolling it: every request

Four steps. The first runs once per hour (token cache). The other three run on every call.
1

Mint a bearer token

Response:
Cache the token in memory. Refresh ~60s before expires_in runs out.
2

Build the JSON-RPC body

Serialize the body once and keep the exact bytes. The bytes you sign must equal the bytes you send.
3

Sign

The signing payload is a second JSON object that wraps the body as a string. Sort keys and keep Python’s default whitespace.
The #1 cross-language gotcha. JavaScript’s JSON.stringify omits spaces after : and ,. Python’s json.dumps includes them. The signing payload above uses Python’s defaults. Sign one shape, server reconstructs the other → HTTP 403 + details.reason: invalid_signature. Use the canonical fixture to verify your implementation in any language.
4

Send with all four headers

All four gates pass → your handler runs.

What can go wrong

The middleware collapses four sub-causes of “signature didn’t verify” into one invalid_signature reason. To narrow it down, re-sign with a fresh timestamp first — that eliminates clock skew and replay. If it still fails, you have a body-byte or key-mismatch issue.
Debugging shortcut — introspect your own token:
Look for active: true, client_id == your DID, and exp > now. Anything off here is your bug.

Canonical fixture

Use this to verify your sign-and-encode implementation against every other Bindu caller, in any language. Signing payload (note spaces after : and ,):
Expected base58 signature:
Your code matches → ship it. Doesn’t match → you’re missing the spaces, your keys aren’t sorted, or your base58 alphabet is wrong (Bindu uses the Bitcoin alphabet — nacl-base58 is the same).

What’s next

Security Stack

How mTLS, Hydra, and DID signatures compose on a single request

DID Identity

The signing side in depth — Ed25519, canonical JSON, key rotation
Sunflower LogoFour headers, four gates, one chain of trust.