# ABCPro Integration Contract — v1.0 (CANONICAL) > **This document is the single source of truth for connecting any product to ABCPro.** > ABCPro is the parent company and the system of record for money, identity, ranks and > payouts. **Partner products conform to this contract — not the other way around.** A > product does not tell ABCPro what to build; it fills out a **Product Profile** (§11) > declaring its own economics, and builds its side to the mechanism defined here. > > Only ABCPro changes this contract, and only with a version bump (§13). Anything a > product needs that isn't here is a change **request** to ABCPro, not a given. --- ## 1. The ownership split (read this first) Every integration has two kinds of decisions. They have different owners: | | **The mechanism** | **The economics** | |---|---|---| | **Owner** | **ABCPro** (this contract) | **The product** (its Product Profile) | | Covers | endpoint, auth/signing, payload *fields*, idempotency, reversals, the pool→commission law, SSO token shape, ledger precision | what counts as *commissionable* revenue, the pool **%** or amount, its `transaction_type` names, its `external_id` scheme, its landing path, its catalog entries | | Why | one uniform, enforceable money surface across all products | only the product knows its own pricing and what it's willing to pay | A product **reports its economics back** so ABCPro can configure the project row and sanity-check it against the contract. That's the whole product-authored surface. --- ## 2. Onboarding process 1. **Product submits a Product Profile** (§11) — its economics, in the template's shape. 2. **ABCPro registers the project** and issues credentials (§4): `project_key`, an API key (`ABCPRO_KEY_ID` + `ABCPRO_SECRET`), and a shared `ABCPRO_SSO_SECRET`. 3. **Product builds to this contract** — sale reporting (§5–6), refunds (§7), **completeness: a `seq` on every report + a daily manifest (§8)**, and SSO (§9) — using the canonical client `connectors/abcpro_connector.php` (copy it; do not re-implement the HMAC — it already handles `seq` and the manifest for you). 4. **Sandbox — test with a TEST key.** ABCPro issues a test key (`ak_test_…`). Reports signed with it run through ABCPro's *real* engine and return the *exact* split a live sale would produce — but nothing is persisted and no money moves (a true dry-run). Perfect your sale reporting, refunds, manifest, and SSO here, as long as you like. (Test-mode reports aren't persisted, so your reconciliation light turns green only once real reports flow on your live key — but confirm your manifest call returns `200` in sandbox.) See §4.5. 5. **Go live.** Once the agreement is signed, ABCPro approves the company for live and issues a live key (`ak_live_…`). Swap the test key for the live key — same code, same endpoints — and real reports flow. Live keys are refused until ABCPro approves; test keys work immediately. The product never needs — and must never be given — ABCPro server, database, or `.env` access. The credentials in §4 plus the connector file are everything. --- ## 3. Non-negotiable laws - **Commissions are paid from the MARKETING POOL, never from gross.** ABCPro pays out of the pool amount the product reports (or a hub rate configured on the project). No pool → no commission. The sale still records for the books. - **All money is integer cents.** The ledger is journaled and reconciled in whole cents (see §6 for the sub-cent rule). - **The product stays merchant of record and keeps its funds at point of sale.** ABCPro never touches the product's checkout or money — it receives *reports* only. - **ABCPro keeps its company share and runs ALL distribution.** The product sends the whole pool and stays ignorant of levels, ranks, downlines, and the company cut. - **Idempotent by `external_id`.** Re-sending the same one is always safe. - **All secrets live in environment config, never in source.** `ABCPRO_SECRET`, `ABCPRO_SSO_SECRET`, your API keys, and every other credential MUST be loaded from a server-side `.env` file (or an equivalent environment-variable mechanism) — never hardcoded in source files, never committed to version control, and never exposed to the browser. Any ABCPro credential found in source or git is treated as compromised and revoked on sight. --- ## 4. Credentials ABCPro issues (per product) ``` ABCPRO_URL=https://abcpro.com ABCPRO_PROJECT_KEY= ABCPRO_KEY_ID= ABCPRO_SECRET= ABCPRO_SSO_SECRET= ``` SSO token audience (`aud`) is always the `project_key`. Keep every credential above in a server-side `.env` file (or equivalent environment config) — never hardcode them in source, never commit them to git, and never expose them client-side (see §3). ## 4.5 Sandbox (test mode) & going live Exactly like Stripe: **the key decides the mode.** Same endpoints, same payloads — no separate URL, no mode flag in your requests. - **`ak_test_…` (test key):** the report is authenticated and run through ABCPro's real engine; the response is the exact split a live sale would return (levels, fee, leadership, onboarding, unallocated) — but **nothing is persisted, no commissions accrue, no rank or payout is touched.** Every response includes `"mode":"test"`. Test reversals are accepted and acknowledged. Use this to perfect auth, payloads, and error handling with zero risk. - **`ak_live_…` (live key):** real reports. Persisted, journaled, attributed, payable. - **Go live = swap keys.** When your agreement is signed and ABCPro approves you, we hand you the live key. Change `ABCPRO_KEY_ID`/`ABCPRO_SECRET` to the live values and you're live — no code change. Until approval, live keys return `403 not approved`; test keys always work. --- ## 5. Auth & signing (every API call) `POST` JSON to the endpoint. Sign the request: ``` X-AbcPro-Key: X-AbcPro-Timestamp: X-AbcPro-Signature: hex( HMAC_SHA256( ABCPRO_SECRET, "." ) ) ``` - The signed base string is **`"."`** — the exact raw bytes you POST. - **Timestamp skew window is ±300 seconds.** Sign at send time; don't reuse old signatures. - Bad/absent signature → `401`. Unknown/revoked key → `401`. Project not active → `403`. The canonical client (`abcpro_connector.php`) does all of this for you. --- ## 6. Reporting a sale — `POST /api/v1/sale.php` One report per commissionable transaction (see each product's profile for its unit). | Field | Type | Req | Meaning | |---|---|---|---| | `external_id` | string | ✔ | **Stable, unique** id for this transaction. Idempotency key. | | `seq` | int | ✔ | **Monotonic report number** — 1, 2, 3 … incremented by exactly one on **every** report you send (**sales AND refunds**), never reused, never skipped. This is how ABCPro detects a report that never arrived (§8) — omit it and lost reports are undetectable, so your integration is incomplete. The drop-in connector sets this automatically; a custom integration **must** include and persist it. | | `gross_cents` | int | ✔ | What the customer actually paid, in cents. | | `fee_cents` | int | ○ | Processor (Stripe) fee withheld on this charge. See pool rules. | | `marketing_pool_cents` | int | ✔* | The pool for this sale (integer cents). *Required unless the project is hub-rate-driven (§6.2). | | `transaction_type` | string | ✔ | The product's declared type (e.g. `retail_sale`, `subscription`, `storage_period`). Must match a row ABCPro configured. | | `ref_code` | string | ○ | The ABCPro referral code attributed to this sale (§10). Omit → recorded, attributed to nobody. | | `buyer_email` | string | ○ | Buyer's email (used for included-account perk exclusion). | | `buyer_external_id` | string | ○ | The product's own id for the buyer. | | `occurred_at` | ISO-8601 | ○ | When it happened. | | `clears_at` | ISO-8601 | ○ | Payout-clearance gate; else project default clearance applies. | | `meta` | object | ○ | Free-form product audit data. **Put authoritative sub-cent figures here** (§6.1). | Response: `200 {ok:true, …}` on success; `4xx {ok:false, error}` otherwise. ### 6.1 The pool & the sub-cent rule (ABCPro-owned) - Report `marketing_pool_cents` as an **integer**. ABCPro's ledger is whole-cent; it will not silently invent sub-cent precision, because that would break reconciliation. - **If your per-report pool would be less than 1¢, you accumulate it on your side and report it when it crosses a whole cent** (or aggregate across the period). The product owns its own rounding/carry — the hub receives whole cents. - Always include the **authoritative unrounded figures in `meta`** for audit, e.g. `meta:{ marketing_pool_microcents: 9000, rate_pct: 30, commissionable_revenue_microcents: 30000 }`. ABCPro journals `meta` verbatim, so nothing is ever *lost* — it just isn't paid below a cent. ### 6.2 Two ways the pool is set (declared per product in the profile) - **Product-computed (default; GigPeach / BluSpots / BluRepo):** the product computes and **sends `marketing_pool_cents`** (typically `pct × (gross − fee)`). ABCPro pays exactly that. No hub rate involved. - **Hub-rate-driven (GuestBookit):** the product **omits** `marketing_pool_cents` and sends `gross_cents` + `fee_cents` + `transaction_type`. ABCPro applies the % configured on the Marketing Pool Rates admin to `(gross − fee)`. The rate is adjustable in ABCPro settings. A product uses one model, not both. If you send a pool, ABCPro uses it and ignores the rate. --- ## 7. Refunds & reversals — same endpoint Send `event` (or `transaction_type`) = one of `refund`, `partial_refund`, `chargeback`, `reversal`, with the **same `external_id`** as the original. ABCPro reverses the commissions and nets the ledger to zero. Period-stamped ids reverse the exact period. > **⚠️ Gotcha — don't let a refund collide with its sale on your side.** Because a refund > reuses the original `external_id`, if you keep your own local report log/queue and > de-dupe it, key that table on **(`external_id` + `event`)**, never on `external_id` > alone. Keyed on `external_id` alone, the refund overwrites/drops against its sale row and > is never sent — ABCPro never reverses the commission and you keep paying on refunded > orders. (The shipped connector's file queue is append-only and immune; this only bites a > custom DB-backed queue.) --- ## 8. Completeness — sequence numbers + the daily manifest (REQUIRED) A commission system that can't tell when a sale report was **lost** will silently over- or under-pay consultants. Completeness is therefore **not optional** — every correct integration does BOTH of the following. Skip them and a dropped report is invisible. ### 8.1 A sequence number on every report (`seq`) - Send `seq` on **every** call to `/api/v1/sale.php` — **sales AND refunds** — as a strictly increasing counter: 1, 2, 3, … Increment by exactly one per report; never reuse, never skip. (A refund is its own report and gets its own `seq`.) - The drop-in connector maintains this automatically. A **custom** integration must persist a durable counter (a row id, or a lock-guarded counter) so it survives restarts and never resets. A queued/retried report keeps its **original** `seq`. ### 8.2 A manifest, at least once a day (`POST /api/v1/manifest.php`) Send your own independent tally so ABCPro can cross-check it against what it received. Signed exactly like a sale report (§5). Body: ```json { "seq_high": 128, "report_count": 128, "gross_total_cents": 45000, "as_of": "2026-09-03T05:43:44+00:00" } ``` - Run it from **cron, at least once per day** (hourly is fine). With the connector: `abcpro_report_manifest(abcpro_current_seq(), abcpro_current_seq());` - `report_count` = how many reports you have sent in total. With a one-per-report `seq` it equals `seq_high`. - `gross_total_cents` is **optional**, but **if you send it, it MUST be NET of reversals** — sum sales positive and refunds/chargebacks **negative**. A $20 sale plus its $20 refund is `0`, not `4000`. A wrong total makes ABCPro flag a mismatch against clean books; when in doubt, omit it — the `seq` gap check alone catches a lost report. - `as_of` is ISO-8601 (`date('c')`); ABCPro stores it in UTC. **What ABCPro does with it:** if it has received every `seq` from `1..seq_high` with no gaps, and at least `report_count` reports, your reconciliation is **green**. A missing `seq` — a report that never arrived — turns it **red** and names the gap. That is the entire point. ### 8.3 Idempotency & retries - `external_id` dedupes. For recurring/period charges, **period-stamp** it (e.g. `…:{YYYY-MM}`) so each period is its own idempotent report. - If ABCPro is briefly unreachable, queue locally and re-send (`abcpro_flush_queue()`) — never block your own checkout on ABCPro. --- ## 9. Single sign-on (SSO) — the token & the consumer's duties ABCPro is the identity provider. It redirects the member to `?t=[&goto=]`. The token is `base64url(json) . "." . base64url(HMAC_SHA256(json, ABCPRO_SSO_SECRET))`. **Claims:** `aud` (must equal your `project_key`), `email` (verified), `name`, **`abcpro_id`** (stable, permanent member id — **key identity on THIS, not email**), `iat`, `exp` (~2 min), `jti`, plus `pro`/`sponsor` for perk accounts. Copy `sso_verify_token()` from the connector verbatim. **Your `sso.php` MUST (these are universal, learned the hard way):** 1. **Discard any existing session** and regenerate the session id — rebuild identity ONLY from the token. Never trust a pre-existing cookie (it can be a *different* member). 2. **Treat the token as email-verification** — no confirmation email, no "verify your account" wall, no password. Auto-provision SSO members **active + verified + passwordless**. 3. **Key the member on the stable `abcpro_id`** — store it; if the token's email differs from a known `abcpro_id`, re-point that member (don't fork a new account). Email is a mutable display field. 4. **Land on your app's default surface** (your dashboard/app home) — never the homepage or a signup wall. Honor `goto` only if it's a safe same-site path; otherwise fall back to your default. Provisioning (`?provision=1`, server-to-server) marks the email verified and returns `{ok:true}` without a redirect. --- ## 10. Attribution (`ref_code`) ABCPro referral codes ride in as `?ref=CODE`. Capture on landing, **persist onto the order** at purchase time (webhooks have no browser cookie), and pass it back as `ref_code` when you report the sale. ABCPro resolves who gets paid — you never compute commissions. --- ## 11. Product Profile — the template a product submits This is the **only** product-authored surface. Fill it out; ABCPro configures the project. ``` Product name: project_key: home_url / sso_url: Commission mode: always | per_event | off Pool model: product-computed | hub-rate-driven Commissionable base: Pool amount / %: transaction_type(s): external_id scheme: Reporting cadence: SSO landing path: Catalog entries: Confirm-back questions: ``` --- ## 12. Confirm-back answers ABCPro gives every product - **`transaction_type` naming** — you propose it in your profile; ABCPro accepts it and creates the matching config row. Keep it stable; it's namespaced per product. - **Sub-cent journaling** — see §6.1: whole-cent reporting, you carry sub-cent remainders, authoritative microcents go in `meta` for audit. ABCPro does not pay below a cent. - **Extra fields** — put anything product-specific in `meta`; it's stored verbatim. New *top-level* fields are a contract change (§13), not a per-product add. --- ## 13. Versioning & change control This contract is versioned (currently **v1.0**). Only ABCPro edits it. Additive changes (new optional field) bump the minor version; breaking changes bump the major and are announced to connected products with a migration window. A product's need for something new is a request to ABCPro, evaluated on the whole network's terms — never a unilateral build. --- ## Appendix A — Current Product Profiles (configured) | Product | Mode | Pool model | Commissionable base | Pool | txn_type(s) | |---|---|---|---|---|---| | **GigPeach** | always | product-computed | per-product (varies) | producer's % of net, sent per sale | `retail_sale` | | **BluSpots** | always | product-computed | per-product/widget (varies) | sent per sale | `retail_sale`, `subscription` | | **GuestBookit** | per_event→rate | hub-rate-driven | direct $20 event (not partner subs) | **50%** of `(gross − fee)` via admin rate | `retail_sale` (50%), `subscription` (0%) | | **BluRepo** | always | product-computed | **NET storage profit** — storage bill − AWS cost − Stripe fee; excludes retrieval | **40%** of net storage profit | `storage_period` (see Appendix B) | ## Appendix B — BluRepo profile (LIVE) - **Unit:** one report per **prepaid 30-day cycle** per attributed customer, on the customer's own anniversary — reported **only when the cycle is actually collected**. - **external_id:** `blurepo:storage:{user}:{cycle_start_date}` where cycle_start_date is `YYYY-MM-DD` (e.g. `blurepo:storage:42:2026-09-01`) — period-stamped → idempotent + reversible. *(Changed 2026-08-30 from `YYYY-MM` to the cycle-start DATE; ABCPro dedupes off the full `external_id` string, so the change is transparent to reconciliation.)* - **Commissionable base:** **NET storage profit** = storage bill ($10 base incl. 1 TB + per-TB overage) − AWS storage cost − Stripe fee on the storage share. Excludes retrieval/restore charges. - **Pool:** `marketing_pool_cents = round(0.40 × net_storage_profit)`, product-computed and sent explicitly (rate **40%**; `meta.rate_pct = 40`, preserved per ledger row). `meta.basis = "net_storage_profit"`; `meta` also carries `storage_revenue_microcents`, `aws_cost_microcents`, `marketing_pool_microcents`, `pool_carry_microcents`. Whole cents only; sub-cent remainder carried per-customer on BluRepo's side (§6.1). - **Example:** 1 TB Vault → $10 − AWS $0.99 − Stripe $0.59 = **$8.42 net** → pool = 40% = **$3.37**. 1 TB Instant → net $5.41 → pool = **$2.16**. - **transaction_type:** `storage_period` — **counts toward rank qualifying volume** (verified: not in `rank_nonqualifying_types`). Real recurring *customer* revenue, so it both funds the pool AND builds rank; only internal FEES are excluded from rank volume. - **Non-payment:** grace → lock → suspend → delete lifecycle; reports fire ONLY on collected revenue (a delinquent cycle reports nothing until paid). Refund/chargeback → `event:"refund"` with the same period-stamped `external_id` (nets that cycle's pool back out). - **SSO landing:** `/app/`. - **Status (2026-08-30):** **LIVE — billing ON (`billing_enabled=1`).** BluRepo's own Stripe account (under the ABCPro org), live keys + webhook verified; prepaid 30-day cycles fully automated. Real `storage_period` reports are flowing to the hub on each collected cycle.