DynamoDB Tables
Storage spans two regions (Infrastructure): dynamo_legacy_region holds the payments table and the other un-prefixed tables; the primary region holds the site-payments single table, site-usio-tokenization-transactions, and site-float-service-log. Clients resolve DYNAMO_LEGACY_REGION for those tables and DYNAMO_REGION + DYNAMO_TABLE for the single table.
payments
One item per payment: every pinless, RTP, and ACH submission that returned a confirmation ID, plus prenotes. Written by the API at submission and updated in place by the syncing mechanisms (Payment Syncing). The table’s DynamoDB stream feeds kinesis-feeder (Events).
| Key / Index | Type | Use |
|---|---|---|
|
PK |
Usio confirmation ID or JPM |
|
GSI ( |
|
|
GSI ( |
check-ach-scheduler’s |
| Attribute | Description |
|---|---|
|
Owning user |
|
|
|
|
|
|
|
See Payment statuses |
|
Dollar amount; |
|
Exactly one identifies the payment class (float, subscription, prorated subscription, loan); prenotes carry none |
|
Bank account ID or debit card ID the payment was drawn on |
|
Official account mask for ACH payments — Account Mask |
|
Last 4 digits for pinless payments |
|
Processor return or decline code and text; set on |
|
Card balance in cents reported by Usio on a code-51 partial-auth decline; absent otherwise |
|
Kinesis event type of the last status change; empty at submission — Events |
|
|
|
Lifecycle timestamps; zero time ( |
|
Float taken as an early advance against this |
|
First time underwriting made the user eligible for an early advance against this payment; set by underwriting-api via the same |
Source: pkg/dynamo/payments.go
Payment statuses
payment_status |
Meaning | Set by |
|---|---|---|
|
ACH accepted by the processor; settlement pending |
API on ACH submission and prenotes |
|
Settled |
API on pinless and RTP success; check-ach-worker |
|
Declined, rejected, or returned; |
API on pinless and RTP decline; jpm-webhook-processor; usio-syncer (returns) |
|
Chargeback reported by Usio |
usio-syncer |
|
Refunded through |
API |
Source: pkg/dynamo/payments.go, pkg/api/refunds.go. Transitions are on Payment Flow.
site-payments (single table)
Keys PK / SK with overloaded indexes GSI1, GSI2, GSI3 (each <name>PK / <name>SK). Item types are distinguished by key prefix and db_type.
| Item | PK | SK | Indexes |
|---|---|---|---|
|
|
GSI1 |
|
|
|
GSI1 |
|
|
|
GSI1 |
|
|
|
— |
|
|
|
— |
|
|
|
— |
|
|
|
— |
|
|
|
— |
|
|
|
— |
|
Refresh crumb |
|
|
— (token-refresh state with a TTL — Token Refresh) |
Debit card (DEBITCARD#)
One item per card; a user may hold several. A card with primary_card_type = ALL is mirrored to pinless-default-card. Flows: Debit Cards.
| Attribute | Description |
|---|---|
|
Owner; FloatMe card ID |
|
Last 4 digits |
|
Hash of last 4 + expiration; fraud deduplication |
|
Usio credit and debit tokens |
|
|
|
RFC3339 |
|
See Card enums |
|
Validity; cleared by the return-code engine, restored by re-adding the card |
|
See Card enums |
|
See Card enums |
|
Bank account linked to the card, if any |
|
Usio |
|
RFC3339; any record carrying it blocks re-adding the physical card — Suspended cards |
Access: cards by user and status (PK + begins_with DEBITCARD, optional status filter); by debit_card_id (GSI3); expiring this month (GSI1 DEBITCARDEXP#<MMYYYY>); every record for a physical card (GSI2). A user with no items here is read from pinless-default-card and the converted card is written back.
Bank account (BANKINFO#)
One item per user and Plaid account_id — Bank Accounts.
| Attribute | Description |
|---|---|
|
Owner; Plaid account ID |
|
KMS-encrypted |
|
Salted SHA-256 of account + routing; plain text for the GSI1 hash lookup |
|
Official account mask; absent on records written before the attribute existed until backfilled |
|
Created or last updated |
Access: by user and account (PK/SK); by hash (GSI1) for fraud checks.
Source: pkg/bankaccount/store/record.go
Card attempt (CARDATTEMPT#)
One item per Usio network response to a pinless debit — the retry-limit ledger. card_key is usio_card_id, else the response’s PaymentAccountID, else LEGACY_<user_id>_<mask>_<expiration>; class is float, sub, or loan.
| Attribute | Description |
|---|---|
|
Owner and tracking-key components |
|
Exact attempt amount |
|
|
|
Cleaned Usio decline code; absent on approvals |
|
Float, subscription, prorated-subscription, or loan ID |
|
Usio confirmation; submission time |
|
|
Access: rolling window per card and class (PK, SK ≥ window start, ttl filter); every attempt for a user (GSI1, hydrated with BatchGetItem).
Source: pkg/dynamo/card_attempt.go
Blocklist (BLOCKLIST#)
db_type = BLOCKLIST. Attributes user_id, status, event_type, trigger_id, date_updated; semantics on Blocklist. Access: current status (SK CURRENT); history (SK begins_with DATE#).
Source: pkg/dynamo/blocklist/blocklist.go
RTP eligibility (RTP#)
Cache of Usio RTP eligibility per routing number: routing_number, rtp_eligible, ttl (30 days). Read and written by GET /{user_id}/payment-methods — RTP.
Source: pkg/dynamo/rtp.go
Usio ACH risk report (USIOACHRISK#)
db_type = USIO_ACH_RISK; one item per merchant account per Chicago calendar day, written by usio-syncer — ACH risk report. No TTL, no event_type.
| Attribute | Description |
|---|---|
|
MID; Chicago date of the fetch; UTC fetch time |
|
Report header counts ( |
|
|
|
The same six fields from the "By Submit Date" section |
Section attributes are absent when the section was not matched; a stored zero is a real zero.
Source: pkg/dynamo/usio_ach_risk.go
Marketing lock
PK = USER#<user_id>, one item per single-fire marketing event, written with a conditional put so the event fires once per user lifecycle:
| Event | SK |
|---|---|
|
|
|
|
PUT /admin/{user_id}/marketing-lock acquires a lock (409 when held) and DELETE releases it (succeeds when not held). The event query parameter picks the lock, defaults to first_debit_card_connected, and an unknown value is a 400. user-service acquires the first-debit lock on prequalify activation and reactivation and releases both on account closure; float-service acquires the first-float lock. Each call records payments.marketing_lock tagged event and outcome (acquired, conflict, released, error).
Source: pkg/dynamo/marketing_lock.go, pkg/api/marketing_lock.go
Refund attempt
db_type = REFUND_ATTEMPT; one item per confirmation ID refunded through POST /refunds, the idempotency record for that refund — Refund idempotency. No TTL.
| Attribute | Description |
|---|---|
|
Payment or staged Usio transaction being refunded; recipient |
|
|
|
Provider’s refund confirmation, set on |
|
Reservation time; completion time |
Source: pkg/dynamo/refund_attempt.go
Usio breadcrumb (USIOBREADCRUMB#)
One item per float or subscription whose last Usio debit submit returned an error; a new failed submit replaces it — Safe retry. Deleted when the search answers landed or not landed. No event_type.
| Attribute | Description |
|---|---|
|
|
|
Owner |
|
|
|
Random value sent in |
|
Payment record data if the charge landed. |
|
Time before the failed submit; the search starts 1 minute earlier |
|
|
Source: pkg/dynamo/usio_breadcrumb.go
pinless-default-card
One item per user (user_id PK): the debit card mirror read when a user has no DEBITCARD# items and written whenever a card with primary_card_type = ALL is saved. Indexes: GSI1 on card_exp (expiring cards for token refresh) and hashed_card_number-index (fraud).
| Attribute | Description |
|---|---|
|
Last 4; hash of last 4 + expiration |
|
Usio credit and debit tokens |
|
Always |
|
|
|
Card state |
Source: pkg/dynamo/debit_card.go (LegacyDebitCard)
pinless-changes
Debit card change history, user_id PK with one row per change_date. Each row is a snapshot of the card after the change: credit_reference_id, debit_reference_id, created_on, is_valid, invalid_date, is_override, masked_card_number, and, on rows written since the attributes existed, suspended_on, usio_card_id, debit_card_id, status. When rows are written: Change history.
Source: pkg/dynamo/debit_card_changes.go
usio-debit-refunds
One item per refund (refund_confirmation_id PK). Indexes: user_id-index; confirmation_id-date_refunded-index. Written by POST /refunds and by the usio-syncer’s tokenization auto-refund, which also writes an item when its refund fails, with a generated refund_confirmation_id and no Usio refund behind it.
| Attribute | Description |
|---|---|
|
Recipient; amount; |
|
Payment record refunded; Usio transaction refunded when no payment record exists |
|
Class of the original payment |
|
Refund reason and the support agent who submitted it |
|
Submission time |
|
|
Source: pkg/dynamo/refunds.go
Usio staging tables
usio-debit-transactions and usio-credit-transactions (dynamo_legacy_region) and site-usio-tokenization-transactions (primary region) hold raw Usio transactions as returned by GetTransactionList, keyed on Confirmation with an EmailAddress-Index GSI, plus MerchantAccountID. Written by usio-syncer (Usio Sync); read by GET /{user_id}/usio/transactions, the AVS check, and refunds.
Source: pkg/dynamo/usio.go
jpm-transaction-details
Raw JPM transaction detail records written daily by jpm-syncer — JPM Transaction Sync. Nothing in this service reads them.
Source: pkg/dynamo/jpm.go
site-float-service-log
Fraud log: PK = <key>#<value>, SK = <RFC3339 timestamp>#<type>, with key in USER, INSTALL_ID, ACCOUNT_HASH, CARD_HASH and type in REQUEST, SUCCESS, FAILED. Queried by PK with SK ≥ now − 24h — Fraud Detection.
Source: pkg/dynamo/fraud_log.go