Security

The parts that are easy to get wrong.

Payment infrastructure fails quietly. A double credit, a payout to the wrong number, a replayed webhook — none of them announce themselves. These are the six decisions Pycue is built around, written down plainly.

Accounts are reviewed before they can move money

Self-service registration does not grant access. A new organization is held at PENDING_APPROVAL: it can sign in, but the dashboard, every portal route and every API key stay closed. An administrator moves it to ACTIVE, and collection and payout access are then granted separately, on request.

  • Registration produces an awaiting-approval account, not a working one
  • Portal routes return 403 ACCOUNT_PENDING_APPROVAL until review
  • Gateway API keys are refused for unapproved organizations
  • Rejection revokes every live portal session immediately

Keys are stored as hashes and shown exactly once

A gateway key is displayed only at issuance. What Pycue keeps is a SHA-256 hash, so a database disclosure does not yield usable credentials. The organization can retrieve its own key later because it is additionally encrypted at rest with AES-256-GCM under a key derived from the session secret.

  • Plaintext keys are never returned by an API after issuance
  • Keys are revocable individually without affecting the organization
  • Live and test keys are distinct and separately revocable
  • Portal passwords are hashed with scrypt and never stored in plaintext

Organization isolation is structural, not conventional

The calling organization is derived from the presented API key, never from a field in the request body. Every query, balance check, ledger write and rate limit is scoped by that identifier. There is no code path that accepts an organization identifier from a client.

  • The same externalUserId in two organizations is two unrelated customers
  • Balances are read-modify-free: every change is an increment or a decrement
  • Balances move only inside the transaction that writes the ledger rows
  • Rate limits are enforced per IP, per organization and per route

Settlement happens exactly once

Money moves only through a conditional status transition — PENDING to SUCCESS, RESERVED to SUCCESS — guarded by a row-level claim inside a single database transaction. A replayed webhook, a retried client request or a concurrent caller cannot double-credit, because only the caller that wins the conditional update performs the paired balance and ledger writes.

  • Idempotency is enforced by a unique (organization, clientReference) index
  • Idempotent replays return the original transaction and the original quote
  • Ledger rows are inserted once and never updated or deleted
  • A release or refund reverses both the balance and any withheld charge

Provider callbacks are treated as untrusted input

Callbacks into Pycue are treated as attacker-controlled and are never acted on as-is. A callback must reference a transaction Pycue itself created, the amount must match what Pycue actually sent, and the status is then re-verified with an authoritative server-to-server call before any money moves.

  • Unknown references and amount mismatches are refused and logged
  • Status is re-verified against the provider before crediting
  • Well-formed callbacks are always acknowledged so the provider does not retry-storm
  • Ambiguous outcomes stay unresolved for reconciliation instead of being guessed

Reconciliation exists because providers are eventually consistent

A payout can be ambiguous — accepted by the provider, then unreachable. Pycue does not guess in either direction, because guessing wrong means either double-paying a customer or freezing legitimate funds. Unresolved transactions can be reconciled on demand by status polling, and a failed payout returns the full reserved amount.

  • Only PENDING, PROCESSING and RESERVED transactions are eligible
  • Reconciliation drives the same exactly-once transitions as the live path
  • Reconciliation can be triggered per transaction, with no background worker required
  • Daily withdrawal limits are checked against the gross, not the net

What we will not claim

Known limits, stated plainly.

Security documentation that lists only strengths is marketing. These are the constraints you should design around.

  • Callbacks are not signed

    There is no signature to verify. Pycue compensates with reference matching, amount matching and authoritative re-verification — but a provider that reported a false success would still have to be caught by that second check.

  • Mobile-money transfers need a fixed egress IP

    Payouts are issued from a restricted set of network addresses, which cannot be guaranteed on infrastructure with dynamic egress. Confirm your deployment can reach the mobile-money networks from a fixed outbound address before planning a payout-heavy launch.

  • Mobile money operators are the source of truth

    Pycue records and routes what the networks tell it. A settlement dispute ultimately belongs with MTN, Telecel or AirtelTigo, not with us.

  • Rate limits are a blunt instrument

    Throttling is per IP, per organization and per route. A shared egress address can cause a legitimate caller to be throttled alongside abusive traffic.

See it in the schema.

The API reference documents every status transition, error code and idempotency rule this page describes — including the ones that only appear under failure.