HIPsHanzo Proposals
Back to HIPs
HIP-1143FinalStandards TrackInterface

Referral — An Edge From Referrer to Referee

Hanzo AI
Created: 2026-08-20
Requires: HIP-0026, HIP-0106, HIP-0139

HIP-1143: Referral — An Edge From Referrer to Referee

Abstract

/v1/referral is referral ATTRIBUTION: who referred whom, and whether that referee ever became a real customer. Every org has a stable code and share link, a new org claims it at signup, and an admin sweep advances signup → qualified once the referee makes metered spend. That attribution record is the whole product — the capability, implemented in hanzoai/cloud at apps/referral, MOVES NO MONEY, and this HIP states why that absence is the design.

Motivation

A referral program needs two facts nobody else records: the edge (referrer ↔ referee, first-touch, once ever) and whether the referee genuinely used the product. Signup alone is a vanity signal; metered spend is the honest one (apps/referral/referrals.go:23-26). What is owed for a qualified referral is an affiliate PAYABLE, tracked in hanzoai/commerce and settled by wire or to a connected wallet — never minted as platform credit.

Specification

The key words MUST, MUST NOT and SHOULD are to be interpreted as in RFC 2119.

The store

One system-namespace SQLite file, referrals.db (sqlpool.Open, cek-encrypted, single-connection): the referral edges plus a code directory. The code itself is a deterministic base32 hash of the org id (apps/referral/referrals.go:17-18), so it never changes and never has to be stored to be reproduced; the directory row only materializes the O(1) reverse lookup.

Addresses

Four operations, all typed ops, across two audiences (apps/referral/referrals.go:117-146):

  • GET /v1/referral — the caller's code, share link and referrals. A pure read: it advances nothing.
  • POST /v1/referral/claim — record a referral from a ?ref code. Idempotent and first-touch: 201 on the first claim, 200 with created:false on replay.
  • GET /v1/admin/referrals/bonuses — SuperAdmin: every edge plus a summary.
  • POST /v1/admin/referrals/sweep — SuperAdmin: the cron path, and the ONLY path that advances a referral; one pass is bounded at 500 so a backlog drains over runs instead of wedging one request.

The two admin leaves sit under a prefix another app also serves — GET /v1/admin/referrals is the affiliates analytics board — so the plugin declares its prefixes from the manifest row (plugin/referral/main.go) rather than the /v1/<name> default, and each gate is bound to the exact path it covers, never a subtree (apps/referral/referrals.go:121-135).

Tenancy

The REFEREE is the validated caller's org, never a client field, and the referrer is resolved from the code — so a caller can only ever attach THEMSELVES to someone else's code. Self-referral is 400, an unknown code 404. Writes with no validated principal are refused ahead of the body decode (requireOrgOnWrite); the two admin leaves require SuperAdmin, fail-closed.

Money

Free, said in those words: plugin/referral/main.go declares cloud.Free, and nothing here meters, gates or debits. The one money-plane touch is a READ — "has this referee spent?" — asked through the payout plane client (apps/referral/commerce.go), and the seam is deliberately read-only: TestCommerceSeamIsReadOnly fails if it ever grows a write method, because a deposit on this interface is exactly how a GET once came to mint platform credit.

Events and telemetry

It publishes nothing to the bus, so a customer's webhooks receive nothing from it. Beyond the request span, a qualification appends one referral.qualified record to cloud's tamper-evident audit trail, best-effort (apps/referral/referrals.go:531-548) — an attestation that a referee became a customer, carrying no amount because this package issues none.

Stage

beta: the manifest row declares it (manifest/apps.go:431, Stage: Beta; HIP-0139 §8).

Upstream

Derives from none.

Rationale

The alternative — the referral system granting credit directly — is the shape every referral program starts with and the one this package was cut back from: once the capability to deposit exists on this surface, a caller eventually reaches it. Splitting attribution (here) from reward (an affiliate payable in commerce) means the worst a compromised referral path can do is mislabel an edge, not print money. Qualification living only on the admin sweep, never on a GET, is the same rule one layer down: reads report state, one bounded idempotent write advances it, and the qualified_at latch makes the transition at-most-once under concurrent sweeps.

Security Considerations

Attribution is an incentive system, so the attacker is a fraudulent referrer. The closures, each specific: the referee comes only from the validated principal, so an attacker cannot claim on a victim's behalf or attach a victim to their code; first-touch-once-ever makes claim replay inert; self-referral is refused on the resolved orgs, not on the code string; and qualification keys on metered spend read from the ledger — a signal that costs real money to forge — never on signup volume. Because no path here mints credit, the residual prize for defeating all of that is a mislabeled attribution row, which the audit trail records with both orgs named.

References

  • HIP-0026 — Identity and Access Management
  • HIP-0106 — The Hanzo Plugin Contract
  • HIP-0139 — Capability

Copyright

Released under CC0 1.0 Universal Public Domain Dedication.