JPM Integration

JP Morgan Chase is used for subscription ACH payments. JPM offers better pricing than Usio for this payment type. All JPM traffic is over the ACH network — there are no pinless or card-present flows.

Authentication

JPM requires mutual TLS (mTLS) for all API calls. Both the client (us) and the server (JPM) present certificates during the TLS handshake.

  • Certificates: Both a TLS client cert and a separate JWS signing cert are stored in AWS Secrets Manager. They are retrieved once per Lambda cold start and reused across invocations.

  • Request signing: Some JPM endpoints require the request body to be signed with a JSON Web Signature (JWS) using the signing cert.

  • Cert renewal: Both certs are created by us and submitted to JPM for renewal annually. Renewal must be coordinated before the existing certs expire — JPM does not auto-renew them.

ACH Payments

All payments submitted to JPM go over the ACH network. ACH is asynchronous — there is no immediate success or failure response. JPM initially accepts the payment (returning a confirmation ID), but the final outcome (cleared or returned) may not be known for several business days.

This means ACH payments require a reconciliation step after submission. JPM provides two mechanisms:

  • Webhooks: JPM sends a callback to our webhook endpoint when the payment status changes.

  • Status polling: Our check-ach-worker Lambda polls JPM daily to check the status of outstanding ACHSENT payments.

See Payment Syncing for how both mechanisms work together.

Payment Statuses

Status Description Business Action

PENDING

Payment received by JPM, pending initial validation.

No action required.

ACCEPTED

Initial validation completed. Payment queued for ACH submission.

No action required.

HOLD

Payment under internal review by JPM (US ACH only).

Monitor; no action required.

CLEARED

Payment cleared JPM internal review and is being sent to the ACH network (US ACH only).

No action required.

COMPLETED

JPM has sent the payment to the ACH network. This is not final — the payment can still be returned by the receiving bank.

No action required. Continue monitoring for returns.

REJECTED

Payment failed JPM validation and was not submitted to the ACH network.

Mark payment as FAILED in DynamoDB. May trigger blocklisting — see Blocklist.

RETURNED

Payment was submitted to the ACH network but the receiving bank returned it. Return code identifies the reason.

Mark payment as FAILED in DynamoDB. Evaluate return code for blocklist action — see Blocklist.

COMPLETED from JPM does not mean the payment successfully debited the user’s account. It means JPM forwarded the payment to the ACH network. RETURNED is the status we receive if the bank rejects it.

Webhook Processing

JPM delivers status callbacks to a dedicated API Gateway endpoint at webhooks.floatme.io. The webhook handler Lambda decouples receipt from processing: it validates the payload, filters to actionable statuses, and enqueues only REJECTED and RETURNED transactions to SQS. All other statuses (COMPLETED, etc.) are acknowledged with HTTP 200 but not forwarded.

JPM
 │  POST /v1/jpm
 ▼
API Gateway (webhooks.floatme.io)
 │
 ▼
jpm-webhook-handler (Lambda)
 │  validate payload
 │  filter: REJECTED or RETURNED only
 ├─ other statuses ──► HTTP 200, no SQS enqueue
 │
 ▼
SQS (jpm-webhooks queue)
 │  trigger
 ▼
jpm-webhook-processor (Lambda)
 │  update payment status → FAILED
 │  write return_code + return_info
 ▼
DynamoDB (prod-payments)

The jpm-webhook-processor looks up the matching payment record by endToEndId (= confirmation_id in DynamoDB), sets payment_status = FAILED, and writes return_code and return_info from the processingInfo field. This triggers a DynamoDB Streams event that kinesis-feeder forwards to the Kinesis stream, which may trigger blocklist processing via blocklist-handler.

The JPM webhook endpoint spec is documented in spec/jpm_webhook_api.yaml. See API Specification for the Swagger UI.

jpm ach flow

Return Codes

JPM uses its own return code scheme that maps to standard NACHA codes. The blocklist system uses these mappings to determine whether a user should be blocked.

JPM Code NACHA Equivalent Meaning

AC04

R02

Account closed.

BE01

R03

No account / unable to locate account.

AC01

R04

Invalid account number structure.

AC06

R16

Account blocked / frozen.

See Blocklist for the full list of codes that trigger auto-blocklisting.

  • Payment Syncing — How webhook callbacks and daily ACH polling keep payment records up to date

  • Blocklist — Which return codes trigger user blocklisting and how blocks are removed

  • Prenotes — Zero-dollar ACH transactions submitted to JPM for bank account verification

  • API Specification — Full OpenAPI spec including the JPM webhook endpoint