Kanko, the gateway mascot
Gateway Docs

Kanko Gateway

Open-protocol payment API for DID-bound callers — AI agents, agent operators, or humans with a digital identity. Identity-bound, idempotent payments with signed receipts and multi-rail settlement.

SDK

@kanko/sdk on npm handles the 402-retry dance.

Quickstart

Clone, docker compose up, and call POST /v0/pay in five minutes.

Who can use kanko?

The protocol is identity-bound, not agent-bound. Any party that can present a verified DID assertion may call the gateway.

API surface

Public routes served by this gateway. The table is loaded from /api-endpoints.json and kept in sync with the controller layer by a build-time test.

Loading endpoint inventory.

Authentication & headers

POST /v0/pay requires proof of DID control, not just a DID string. The caller signs a canonical message binding the money-relevant fields (DID, idempotency key, timestamp, amount, currency, provider URL) with the ed25519 private key behind their did:web document, and presents the signature in X-Kanko-Signature with the matching X-Kanko-Timestamp. The gateway resolves the DID document, verifies the signature against the published key, and rejects a missing or invalid signature with 401 unauthenticated before any rail is touched. The @kanko/sdk libraries compute these headers for you.

Required headers

HeaderRequired onDescription
X-Kanko-Agent-DID POST /v0/pay Caller DID, e.g. did:web:agent.example.com. Must resolve to a DID document whose ed25519 key verifies the request signature.
X-Kanko-Signature POST /v0/pay Required. base64url(no-pad) ed25519 signature over the canonical pay message (see the OpenAPI PayCallerSignature scheme for the exact field order). Proves the caller controls the DID's key.
X-Kanko-Timestamp POST /v0/pay Required. RFC-3339 instant, echoed verbatim into the signed message. Rejected if it skews more than ±5 min from gateway time (replay window).
Idempotency-Key POST /v0/pay Client-chosen unique key (UUIDv4 recommended). Same key + same body returns the cached response; same key + different body returns 409.
Content-Type All POST bodies Must be application/json.

Payment flow

The kanko protocol is a three-party 402 handshake. A provider gates a resource with 402 Payment Required and a WWW-Authenticate: Kanko challenge. The caller calls the gateway to settle, then retries the original request with the receipt.

Create and settle a payment

POST /v0/pay
curl -X POST https://kanko.kaneky.dev/v0/pay \
  -H "X-Kanko-Agent-DID: did:web:agent.example.com" \
  -H "X-Kanko-Timestamp: 2026-07-01T12:00:00Z" \
  -H "X-Kanko-Signature: base64url-ed25519-over-canonical-message" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "provider_url": "https://api.example.com/data",
    "amount_minor": 50,
    "currency": "USD",
    "rail_preferences": ["stripe", "x402"],
    "metadata": { "job_id": "abc-123" }
  }'

The X-Kanko-Signature / X-Kanko-Timestamp pair is proof of DID control — the SDK computes them from your signing key. A hand-rolled client must sign the canonical message itself (field order in the OpenAPI PayCallerSignature scheme); without a valid signature the gateway returns 401.

Response (200 OK)
{
  "receipt_id": "rcpt_01HZabc123def456ghi789jkl",
  "signature": "MEUCIQD...base64url...",
  "receipt_url": "https://kanko.kaneky.dev/v0/receipts/rcpt_01HZabc123def456ghi789jkl",
  "livemode": true
}

In sandbox mode (KANKO_MODE=sandbox) the gateway returns deterministic fake settlements with livemode: false and makes no external rail calls — the honest way to demo the full pay → receipt round-trip without credentials.

Rail status. stripe is a preview rail (settles a real Stripe PaymentIntent in live mode). x402 is a sandbox-only stub: in live mode it refuses with 503 rail_unavailable rather than sign a receipt for a settlement that did not happen. Prefer stripe for a live demo; use either in sandbox.

Identity verification

Verify a did:web identity assertion and optional verifiable credential. The response tells you the resolved DID, KYC tier, and whether the assertion signature is valid.

POST /v0/identity/verify
curl -X POST https://kanko.kaneky.dev/v0/identity/verify \
  -H "Content-Type: application/json" \
  -d '{
    "assertion": {
      "issuer": "did:web:agent.example.com",
      "subject": "did:web:agent.example.com",
      "issued_at": 1718900000,
      "expires_at": 1718903600,
      "nonce": "base64url-nonce"
    },
    "signature": "base64url-ed25519-sig",
    "verifiable_credential": null
  }'
Response (200 OK)
{
  "agent_did": "did:web:agent.example.com",
  "kyc_tier": "email_verified",
  "verified": true,
  "reason": "verified",
  "skew_seconds": 0
}

Receipts

Receipts are signed JSON bodies. Fetch one by its public id to verify it on the provider side, or render it as a PDF for human review.

GET /v0/receipts/{id}
curl https://kanko.kaneky.dev/v0/receipts/rcpt_01HZabc123def456ghi789jkl

The receipt carries the gateway's ed25519 signature and a signing_key_id. Verify the signature offline with the key published at GET /.well-known/kanko-public-key, or use the JWK Set at GET /.well-known/kanko-public-keys for historic receipts signed under retired keys.

Webhooks

Inbound: Stripe posts events to POST /webhooks/stripe. The gateway verifies the Stripe signature before persisting the event.

Outbound: Partners can receive signed lifecycle events (receipt.settled, receipt.refunded, receipt.disputed) by registering a DID-to-URL mapping. Each push carries Kanko-Signature, Kanko-Key-Id, and Kanko-Event-Type headers and can be verified against /.well-known/kanko-public-keys.

Gateway build info

Runtime banner from GET /banner.