Getting started

Verifying the payer

Every deposit request names who may pay and what they must prove first. You assert; Payday confirms; and whatever the policy, the payer signs once from the wallet they will pay from before an address exists.

Verification answers a question a bank transfer never could: was the money sent by the person you were expecting, and did they see what they were paying for? Payday splits that into two parts. The payer policy decides what a person proves before the request's content is shown to them. The wallet step then ties that person to the wallet the funds will come from.

The three policies#

payer_policy
{ "mode": "permissionless" }
{ "mode": "verified_email", "expected_email": "alice@customer.example" }
{ "mode": "merchant_session", "payer_reference": "user_123" }

Permissionless

open link

Anyone holding the link sees the request and can pay it. Nothing is proved beyond the wallet step.

Use it for a one-off invoice you email yourself, or when the link is handed over in a channel you already trust.

Verified email

emailed code

You name the mailbox. The checkout shows only your name and the heading until the payer types a code sent to that mailbox. Payday sends the code; the payer never types an address.

Use it when a request must only be seen and paid by a specific person you know by email.

Merchant session

your sign-in

Your application has already signed the user in. It creates the request naming that user by your own id, receives a single-use client secret, and opens the checkout with it. The page opens unlocked; the payer types nothing.

Use it for an exchange, a fund, or any product crediting a logged-in customer.

The merchant asserts, Payday confirms. Payday tells you whether the check passed and when, never the payer's own data. The full policy, including the expected email or the payer reference, is returned only to you; the payer page shows a masked hint such as a****@c***.example for verified email, and nothing at all for a merchant session.

What the payer sees, and when#

A gated request discloses progressively. Until the policy is satisfied, only the issuer name and heading are shown; until the wallet is bound, there is no address to show.

ShownAlwaysAfter verificationAfter the wallet step
Issuer name and heading
Status, deadline, whether it is payable
Amount, remaining, chain, token
Payer name, notes, reference, PDF
One-time address, QR, wallet button
Disclosure by moment. A permissionless request starts in the middle column: everything but the address is visible at once.
  • Your payout address
  • The recovery address
  • Metadata
  • The customer record
  • The expected email (only a masked hint)
  • The payer reference
Never shown to a payer, whatever the policy.

Verified email, step by step#

Payer's browserPaydayAsserted mailboxopen deposit_urlissuer name and heading onlystart email verificationsix-digit codepayer_sessionopaque, 24h, stored hashedconfirm codecontent unlockedwallet challenge → sign → attestone-time address
The emailed-code exchange. The payer's browser holds a session token, which travels in a header on every later read and unlocks exactly this request.
  • One code per request per minute, whoever asks. A wrong, spent, or expired code fails.
  • The code is delivered by Payday's email provider from a Payday domain and names no deposit data.
  • Funds sent before verification completes still count, but the request is flagged likely unsolicited, because the attested wallet did not pay them.
  • After settlement, the same exchange re-proves the mailbox to reopen the receipt; it never makes the request payable again.

Merchant session, step by step#

This is the mode for applications with their own sign-in. Your server creates the request, receives a client_secret once, and sends the signed-in user to the checkout with that secret in the URL fragment. Exchanging the secret is the verification.

Your serverPayer's browserPaydaycreate { mode: merchant_session, payer_reference }201 + client_secret (once, 15 min)redirect to deposit_url#cs=…exchange client_secretspent on first usepayer_session, page unlockedwallet challenge → sign → attestUSDC transfer to the addresswebhooks with payer_reference
The merchant-session exchange. The secret rides in the URL fragment, which browsers never send to a server, so it reaches no log, Referer header, or analytics beacon.
  • The secret is returned exactly once, on the 201. It is never on a replay or a later read; Payday stores only its hash. If your server loses it, mint another with POST /v1/deposit-requests/{id}/client-secret.
  • A secret is spent by its first exchange. The same link pasted into a second window is refused, and the page says so. When the user comes back later, mint a fresh secret and redirect again.
  • Every webhook for the request carries your payer_reference, so a handler credits the right ledger with no lookup.
  • What Payday attests is narrow and stated plainly: your server released this secret, and it was exchanged before this session saw the request. Who the payer is remains your assertion.

The wallet step#

Whichever policy a request has, the payer signs one message before the address exists. It is an EIP-712 typed-data signature, the kind every wallet displays in full before signing: a plain statement, the request's attribution hash, the wallet address, a one-time nonce, and an expiry. No transaction, no gas, no approval.

FieldWhat it is
statementA sentence the wallet shows: what the payer is agreeing to.
attributionHashThe hash of the issued document, so the signature belongs to this request only.
walletThe address that will pay. The signature must recover to it.
nonceMinted for this session once the policy is satisfied, valid for ten minutes. It is what ties the person who passed the checks to the wallet.
expiresAtWhen the challenge is void.

Once accepted, the salt, the recovery term (the wallet), and the address are written together, once. A deposit_request.ready webhook reports it, and the checkout shows the address, the QR, and the pay button. The pay button refuses to send from any wallet but the attested one.

Reading verification state#

Every request reports verification separately from its status:

  • verification_completed_at: when the policy was satisfied; null until then, and always null for permissionless requests;
  • likely_unsolicited_at: when finalized funds first arrived from a wallet other than the attested one;
  • payer_wallet and wallet_bound_at: the attested wallet and when it was bound;
  • GET /v1/deposit-requests/{id}/verification: each fact on its own (email, merchant session, wallet) and every attempt the payer made, with its outcome and time. Never the code, the secret, or the session.

The list route filters on verification=not_required|pending|verified|likely_unsolicited alongside status, and webhooks raise verification.approved, deposit_request.ready, and deposit_request.likely_unsolicited the moment each fact is first recorded.