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 |
|---|---|---|
|
Insufficient Funds |
Should not occur on a zero-dollar prenote, but some banks return it in error. Does not necessarily indicate an invalid account. |
|
Account Closed |
Account exists but has been closed. Triggers user blocklisting. See Blocklist. |
|
No Account / Unable to Locate Account |
Account number does not match any account at the receiving bank. Triggers user blocklisting. |
|
Invalid Account Number Structure |
Account number fails a structural validation check. Triggers user blocklisting. |
|
Authorization Revoked by Customer |
Customer has revoked prior authorization. Does not apply to prenotes but may appear in related ACH flows. |
|
Account Frozen |
Account is frozen and cannot receive transactions. Triggers user blocklisting. |
|
Non-Transaction Account |
Account type (e.g. savings) does not allow ACH transactions. |
|
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.
Related Pages
-
JPM Integration — JPM ACH payment details and return code mappings
-
Usio Integration — Usio ACH and return rate thresholds
-
Blocklist — Automatic blocklisting triggered by specific return codes
-
Payment Syncing — How prenote outcomes are resolved via ACH polling
-
DynamoDB Tables —
bank-accountsschema (account and routing storage)