Trust

How Payday works

What happens between a deposit request and a settled deposit, and the properties that hold along the way. Written for the person deciding whether to build on Payday, with as much detail as that decision needs.

In one paragraph#

Payday never holds funds. Each request gets a one-time smart-contract address whose settlement terms are fixed before it exists, derived from the issued document and the payer's own signature. A finality-gated indexer reads USDC transfer events from the chain into a durable ledger and updates each request from that ledger. When a request is funded or expires, a settlement transaction executes the contract, which can only move the funds under its own fixed terms: the amount to your wallet, and anything else to the payer's wallet. Every step is recorded, and a settled request yields a proof that can be checked without Payday.

No custody#

What Payday holds

nothing of yours
  • Your deposit requests, customers, and identities
  • A ledger of every observed transfer
  • Hashes of keys and secrets, never the values
  • An operator wallet that pays gas for settlement transactions

What Payday never holds

by construction
  • The requested amount, which moves address to your wallet
  • Returns, which move address to the payer's wallet
  • Your wallet's keys, or the payer's
  • Plaintext API keys, webhook secrets, or verification codes

The operator wallet submits settlement transactions and pays their gas. It cannot redirect funds: the deposit contract reads its destination, amount, deadline, and recovery address from the terms committed into its own address, and anyone may execute it with the same result. A compromised operator key could at worst pause settlement, not divert it.

Terms fixed in the address#

The one-time address is a CREATE3 address: it depends on a factory contract and a salt, and the contract that will live there is deployed only when it is time to settle. Payday derives the salt from two things: the hash of the issued document, canonicalized with RFC 8785 so that the same document always hashes the same way, and the EIP-712 digest of the payer's wallet attestation over that hash.

Issued documentparties, amount, deadline…canonicalizeAttribution hashRFC 8785 + keccakPayer's walletthe one that will payEIP-712 signsAttestation digesthash + nonce, signedSaltkeccak(hash ‖ digest)CREATE3One-time addressexists before a contractTerms the address commits totoken · amount · payout · deadline · recovery walletAnyone holding the proof can recompute every step offline.Nothing in the chain of derivation can be changed after the payer signs.
The derivation. Because the payer's signature is an input, no address exists until a wallet has committed to the request, and because the terms are inputs, no one can deploy a contract at that address with different terms.
  • Immutability. Changing the amount, the payout address, the deadline, or the recovery address would change the address. An issued request cannot be edited; it is cancelled and reissued.
  • Counterfactual. USDC can arrive before any contract exists at the address. Deployment later routes whatever balance is there under the committed terms.
  • Recovery is the payer. The recovery term is the wallet that signed, so an overpayment remainder, an expired balance, or a late transfer returns to the payer on-chain without anyone deciding where it should go.
  • Settlement needs only the salt. The attribution material exists so the document and the wallet can be proven afterwards, not so that funds depend on it.

Detection and crediting#

Payday runs its own indexer against the chain. It does not download blocks or trust a third-party notification; it queries the USDC contract's Transfer event logs for the addresses it is expecting, in bounded block ranges, up to the chain's finalized boundary.

ChainUSDC Transfer logsfinalized onlyIndexerexact token and chainappendLedgerevery transfer, foreverRequest statestatus, receivedeligible: amount met, or deadline passedSettlementone batched transactionDeposit contractterms fixed in the addressPayout and returnsamount → you · rest → payer
From chain to settlement. The ledger is append-only and every observation is identified by chain, token, transaction, and log index, so a transfer can never be credited twice.

A transfer is credited only when all of the following hold:

  1. It was emitted by the exact USDC contract configured for the environment.
  2. Its recipient is a known deposit address.
  3. Its block is at or below the finalized boundary.
  4. Its block timestamp is no later than the request's deadline.
  5. It has not been recorded before.

A transfer that fails the deadline test is recorded as late and queued for return. A transfer from a wallet other than the attested one is credited but flags the request likely unsolicited. There is no privileged way to mark a request paid: the ledger is the only input.

PropertyGuarantee
FinalityNothing is credited from a block that can still be reorganised.
Exact assetUSDC is identified by chain id and contract address, never by symbol or name. Upgrades to the USDC proxy are monitored.
IdempotencyEvery observation has a unique identity; retries and restarts cannot double-count.
Chain timeDeadlines are judged by block timestamp, not by any clock Payday or the payer holds.
Halt on doubtIf the indexer's stored position stops matching the chain, or a request cannot be settled safely, movement pauses and the request reports needs_attention rather than guessing.

Settlement#

A funded or expired request is claimed by a settlement worker, batched with others, and executed in one transaction through the factory. The transaction's finalized receipt, not its submission, determines the outcome recorded against each request. Execution at or before the deadline sends exactly the amount to your payout address and any remainder to the payer; execution after it sends the whole balance to the payer. Funds arriving after execution are forwarded to the payer by the same contract.

Because the contract enforces this, a settlement transaction submitted by anyone, including a payer or an auditor holding the salt, has the same effect. Payday's worker exists so that no one has to.

Verification#

Payday separates who a payer is from which wallet paid, and is precise about which of the two it vouches for.

FactEstablished byWho can check it
The mailbox was openedA one-time code from an established identity provider, exchanged by Payday. The payer never names an address; it goes to the one you asserted.Payday attests it, signed into the proof.
Your application released the secretA single-use client secret, stored hashed, spent on first exchange.Payday attests it; the reference is your assertion.
This wallet committed to this requestAn EIP-712 signature over the document hash and a nonce issued only after the policy passed.Anyone, from the proof.
This wallet paidTransfer events from that wallet to the address.Anyone, against the chain.

The result is the Proof of Payment: the recomputable parts are recomputable, the attested parts are signed and bound to that request and that payer, and Payday declines to issue a proof it cannot stand behind.

Credentials and secrets#

API keys#

  • Shown once. Stored as a SHA-256 digest with a six-character hint.
  • One active key per account; rotation keeps the previous key for 24 hours.
  • Minting and revoking need a signed-in merchant, never a key. Whoever holds a key cannot widen their own access.
  • Headers and bodies are never logged, which keeps keys out of logs.

Merchant sign-in#

Merchants sign in through an established wallet-and-identity provider with an emailed code. The provider creates the account's embedded wallet and holds its key material; Payday verifies the provider's signed identity token on every request with the provider's published keys, and refuses to start if it cannot fetch them. Payday never sees a password and never holds a merchant's wallet key.

Payer sessions and codes#

  • Verification codes are created and checked by the identity provider; Payday sees the code only to pass it on, and stores neither it nor the mailbox beyond the proof it needs.
  • Session tokens are opaque, 24-hour, stored hashed, and bound to one request.
  • Client secrets are returned once, stored hashed, spent on first use, and carried in URL fragments so they never reach a server log.
  • One code per request per minute limits guessing; a wrong code reveals nothing.

Webhook secrets#

Returned once at registration. Stored as a hash for identification and as an authenticated ciphertext, with a versioned key identifier, for delivery. Endpoint URLs are checked against private and reserved networks at registration and again before every delivery.

Attachments#

PDFs go straight to object storage through a presigned, write-once upload. They are scanned for malware before they can be attached, hashed by Payday from the stored bytes, and pinned to the exact object version admitted. A rejected file is deleted.

The hosted checkout and dashboard#

  • A deposit link is public by design and carries no merchant data. For a gated request, withheld fields are absent from the response, so the page cannot render them by accident.
  • Pages are served with a strict Content-Security-Policy, cannot be framed, and send no Referer. The checkout's pay button sends a plain USDC transfer of the amount still due: no approval, no contract call, nothing that could redirect funds.
  • The dashboard holds no API key. It authenticates with the merchant's session and renders nothing on the server, so no request or token is ever in a page's HTML.

Operations#

  • Production and sandbox are separate deployments with separate databases, keys, signers, and identity providers.
  • The API answers /health for readiness and /v1/status for chain and indexer position, and every response carries a request id.
  • Structured logs record method, path, status, latency, and account id. Never headers or bodies.
  • The core library that derives addresses and verifies proofs is open source, so the checks a proof relies on can be read and run by anyone.