Prenotes

A prenote is a zero-dollar ACH transaction submitted to verify that a bank account is valid and capable of receiving real ACH payments. It exercises the same ACH network path as a live payment — including the same NACHA return code failure modes — without moving any money.

Why Prenotes

  • Account validation: Identifies closed, frozen, or invalid accounts before risking a real payment. A returned prenote saves the cost and return-rate impact of a failed live ACH.

  • Return rate improvement: Prenotes count toward successful ACH submissions. A higher ratio of successful submissions lowers the overall return rate, helping keep it within JPM and Usio thresholds.

  • Cost: Prenotes are not free — each one incurs a small processing fee. This is a trade-off against the higher cost of failed live ACH payments.

Some bank accounts incorrectly return R01 (Insufficient Funds) on prenotes despite the zero-dollar amount. This is a bank-side error and does not mean the account is invalid for real payments.

Submission Flow

Prenotes are submitted through the Payments Service REST API. The API endpoint fetches the encrypted bank account details from DynamoDB, decrypts them with KMS, and submits the prenote directly to the payment processor.

Caller (other FloatMe service)
 │  POST /prenotes/jpm  or  POST /prenotes/usio
 ▼
Payments API (Lambda)
 │  fetch bank account from DynamoDB
 ▼
DynamoDB (bank-accounts)
 │  account + routing (KMS-encrypted)
 ▼
Payments API
 │  decrypt with KMS
 │  submit prenote
 ├──► JPM ACH API  (amount = $0.00)
 └──► Usio ACH API (amount = $0.00)
         │
         ▼
  Payments API
  │  save payment record (status = ACHSENT, amount = 0)
  ▼
DynamoDB (prod-payments)

After submission, the prenote is tracked as an ACHSENT payment record with event_type = PRENOTE_SUBMITTED and amount = 0. Its outcome is resolved through the normal ACH syncing process — either via JPM webhooks or daily check-ach-worker polling.

Batch Prenote Flow

The prenoter Lambda handles bulk prenote submission. It dequeues messages from SQS and calls the Payments API endpoint for each one:

Caller
 │  send message to SQS
 ▼
SQS (prenotes queue)
 │
 ▼
prenoter (Lambda)
 │  POST /prenotes/jpm  or  POST /prenotes/usio
 ▼
Payments API  →  (same flow as above)

NACHA Return Codes

Prenotes share the same NACHA return code set as live ACH payments. The following codes are most relevant to prenotes:

Code Name Notes

R01

Insufficient Funds

Should not occur on a zero-dollar prenote, but some banks return it in error. Does not necessarily indicate an invalid account.

R02

Account Closed

Account exists but has been closed. Triggers user blocklisting. See Blocklist.

R03

No Account / Unable to Locate Account

Account number does not match any account at the receiving bank. Triggers user blocklisting.

R04

Invalid Account Number Structure

Account number fails a structural validation check. Triggers user blocklisting.

R07

Authorization Revoked by Customer

Customer has revoked prior authorization. Does not apply to prenotes but may appear in related ACH flows.

R16

Account Frozen

Account is frozen and cannot receive transactions. Triggers user blocklisting.

R20

Non-Transaction Account

Account type (e.g. savings) does not allow ACH transactions.

R29

Corporate Customer Advises Not Authorized

Receiving bank rejected the transaction on behalf of the account holder.

Return codes R02, R03, R04, and R16 trigger automatic blocklisting when observed on either prenotes or live ACH payments. See Blocklist for the full blocklist trigger logic.