Request for Payment Transaction

Instant Pay In (RTP / Request for Payment)

Instant Pay In lets you collect funds from your users in real time using Request for Payment (RFP) over the RTP network, instead of waiting for ACH settlement.

If the user’s bank supports RFP:

  • Aeropay sends a payment request to the user’s banking app.
  • The user approves the request.
  • Funds move instantly to Aeropay.
  • Aeropay then pays out your merchant account via RTP.

If RFP is not available for the user’s bank, we automatically fall back to ACH.


How it works

  • Step 1 – Authenticate using POST /v2/token to get a JWT (30-minute TTL).
  • Step 2 – You create a transaction via POST /v2/transaction with rtp=true.
  • Step 3 – If RfP eligible (feature flags, routing number, KYC/PII, provider status), Aeropay:
    • Creates a transaction.
    • Sends an RFP to the user’s bank. The RFP has a 15 minute expiration window.
    • Returns 200 OK with a transaction object and transaction.isRtp=true (RTP rails) when applicable.
  • Step 3 – If not RfP eligible, Aeropay:
    • No RfP is initiated. Transaction falls back to ACH.
  • Step 4 – You listen for webhooks:
    • transaction_rfp_delivered when the RFP reaches the user’s bank.
    • transaction_completed when the user approves and funds clear.
    • transaction_rfp_rejected or transaction_rfp_expired if the user declines or does nothing.
  • Step 5 – (Not always applicable) Aeropay pays you out via RTP, and you receive:
    • payout_rtp_initiated
    • payout_rtp_processed (or payout_rtp_failed)

Webhooks may arrive slightly out of order, so always use the latest event for a given transactionId or payoutId.


PII Requirement

When creating an Instant Pay In transaction (rtp=true), the userIdentification object is required. It provides the PII needed for RFP eligibility verification with the user's bank.

You must include one of the following options:

{
  "userIdentification": {
    "birthday": {
      "date": "1962-11-05", // Required. Date of birth in ISO 8601 format (`YYYY-MM-DD`)
      "country": "US", // Required. Two-letter country code (ISO 3166-1 alpha-2)
      "city": "Chicago" // Optional. City of birth
    }
  }
}
OR
{
  "userIdentification": {
    "address": {
      "streetName": "123 Main St", // Required. Street address including number
      "city": "Chicago", // Required. City name
      "state": "IL", // Required. Two-letter state abbreviation
      "postalCode": "60601", // Required. ZIP or postal code
      "country": "US" // Required. Two-letter country code (ISO 3166-1 alpha-2)
    }
  }
}

Rules and Guidance

  • Required only for Instant Pay In -- If rtp is omitted or false, userIdentification is not needed.
  • Provide one option -- Either address or birthday. If both are provided, Aeropay may use either for eligibility checks.
  • Reuse existing KYC data -- If you already collect this information during onboarding or KYC, pass it here without re-prompting the user.
  • PII is never returned -- userIdentification data does not appear in transaction responses or webhook payloads.
  • Treat as sensitive -- Do not log or persist these fields in your own systems beyond what is required for compliance.

1. Creating an Instant Pay In transaction

Instant Pay In uses the existing Aeropay v2 transaction endpoint, plus an additional rtp flag.

Get a token (JWT)

Tokens last for 30 minutes and must be sent as Authorization: Bearer {{token}}.

Merchant-scoped token

POST /v2/token HTTP/1.1
Host: api.aeropay.com
Content-Type: application/json
{
  "apiKey": "{{api_key}}",
  "apiSecret": "{{api_secret}}",
  "scope": "merchant",
  "id": "{{mainMerchantId}}"
}

UserForMerchant-scoped token

To create transactions on behalf of a specific paying user, request a userForMerchant token.

POST /v2/token HTTP/1.1
Host: api.aeropay.com
Content-Type: application/json
{
  "apiKey": "{{api_key}}",
  "apiSecret": "{{api_secret}}",
  "scope": "userForMerchant",
  "id": "{{mainMerchantId}}",
  "userId": "{{userId}}"
}

Request

POST /v2/transaction HTTP/1.1
Host: api.aeropay.com
authorization: Bearer {{userForMerchantScopedToken}}
Content-Type: application/json
Idempotency-Key: 2b7b8452-79d1-4c5a-b326-03fb7aaf7a34
{
  "merchantId": 582,
  "amount": {
    "amount": 5000,
    "currency": "USD"
  },
  "rtp": true,
  "referenceId": "order-12345",
  "userIdentification": {
      "address": {
        "streetName": "123 Main St",
        "city": "Chicago",
        "state": "IL",
        "postalCode": "60601",
        "country": "US"
      }
    }
}

Response (RFP path taken)

{
  "transaction": {
    "id": "f79c19d4-11fd-408e-940f-c341667563b9",
    "amount": {
      "amount": 5000,
      "currency": "USD"
    },
    "status": "pending",
    "paymentType": "payment",
    "userId": "2ac82269-9315-4bb6-8a19-f37e20d50238",
    "referenceId": "order-12345",
    "isRtp": true,
    "merchantId": 582,
    "locationId": 541,
    "userAccountId": 1139036,
    "rfp": {
      "rfpId": "rfp_479628e8-57e2-476a-b4fc-b2c70153c7b4",
      "status": "queued",
      "expiresAt": "2025-03-20T20:15:00Z"
    }
  }
}

Response (fallback to ACH)

{
  "transaction": {
    "id": "5c10f370-387a-4aa0-8f94-56fecc4b0d84",
    "amount": {
      "amount": 5000,
      "currency": "USD"
    },
    "status": "pending",
    "paymentType": "payment",
    "userId": "2ac82269-9315-4bb6-8a19-f37e20d50238",
    "referenceId": "order-12345",
    "isRtp": false,
    "merchantId": 582,
    "locationId": 541,
    "userAccountId": 1139036,
    "rfp": null
  }
}

You should:

  • Treat isRtp and rfp as advisory flags.
  • Drive your UI primarily from webhooks, not only from the initial response.

2. Webhooks

You must configure a webhook subscription using the existing Aeropay v2 webhooks API.

Create a webhook subscription

Use POST /v2/webhook with a merchant-scoped token.

POST /v2/webhook HTTP/1.1
Host: api.aeropay.com
authorization: Bearer {{merchantScopedToken}}
Content-Type: application/json
{
  "topic": "transaction_rfp_delivered",
  "url": "https://yourapp.example.com/webhooks/aeropay"
}

Event payload

All Instant Pay In events share a common envelope (delivered to your subscribed URL):

{
  "id": "whk_01HZYQZ1G7R0K1ZZ2EH7A5MCCZ",
  "type": "transaction_completed",
  "version": "1.0",
  "createdAt": "2025-03-20T19:15:30Z",
  "data": {
    "...": "event-specific payload"
  },
  "signature": "v1=HEX_SIGNATURE"
}

Verify the signature using the webhook secret we provide in your dashboard.

Transaction events

  • These topics are additive to existing v2 webhook topics (for example transaction_completed, transaction_voided). Your integration can subscribe to both standard transaction topics and Instant Pay In–specific topics.

  • transaction_rfp_initiated: RFP has been created and sent to our provider.

  • transaction_rfp_delivered: RFP has been delivered to the user’s bank.

  • transaction_completed: User approved the RFP and funds have posted.

  • transaction_rfp_rejected: User rejected the RFP in their banking app.

  • transaction_rfp_expired: User did not act before the 15 minute expiration; the RFP is no longer valid.

  • transaction_voided: The transaction will not complete (for example, failed delivery).

Example: RFP delivered

{
  "id": "whk_01HZYQZ1G7R0K1ZZ2EH7A5MCCZ",
  "type": "transaction_rfp_delivered",
  "version": "1.0",
  "createdAt": "2025-03-20T19:02:00Z",
  "data": {
    "transactionId": "f79c19d4-11fd-408e-940f-c341667563b9",
    "merchantId": 582,
    "rfpStatus": "delivered"
  },
  "signature": "..."
}

Example: Transaction completed

{
  "id": "whk_01HZYQZ1G7R0K1ZZ2EH7A5MCCZ",
  "type": "transaction_completed",
  "version": "1.0",
  "createdAt": "2025-03-20T19:10:30Z",
  "data": {
    "transactionId": "f79c19d4-11fd-408e-940f-c341667563b9",
    "merchantId": 582
  },
  "signature": "..."
}

Payout events (Not always applicable)

Depending on the financial institution your merchant account is set up with, there may be an extra step involved in moving funds to your merchant bank account.

RfP payments may happen in two parts: first, the instant payment from the user to Aeropay, and then an instant payment from Aeropay to your merchant bank account. The following events are sent when Aeropay sends the instant payment to your merchant account.

  • payout_rtp_initiated: We initiated RTP payout to your merchant bank.
  • payout_rtp_processed: Payout successfully posted to your bank.
  • payout_rtp_failed: Payout failed; our team will investigate and may contact you.

Example: Payout processed

{
  "id": "whk_01HZYQZ1G7R0K1ZZ2EH7A5MCCZ",
  "type": "payout_rtp_processed",
  "version": "1.0",
  "createdAt": "2025-03-20T19:11:10Z",
  "data": {
    "merchantId": 582,
    "transactionId": "f79c19d4-11fd-408e-940f-c341667563b9",
    "status": "processed"
  },
  "signature": "..."
}

3. Adaptive Polling

In order to properly surface the correct information and maintain alignment on your merchant app during the RfP process, your merchant app should begin adaptive polling (e.g. every 5 seconds). Use the GET v2/transaction/txn_uuid to poll for status updates to the transaction.

curl --request GET \
     --url https://api.sandbox-pay.aero.inc/v2/transaction/47e1eab8-5b71-42e7-a0fd-bf30f73af6fb \
     --header 'Content-Type: application/json' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer {{token}}'

4. Idempotency and retries

  • Idempotency: Pass a unique Idempotency-Key header (and/or idempotencyKey field) per logical transaction. Retries with the same key will return the original result.
  • Webhook retries: If your webhook responds with a non-2xx status, we will retry delivery with exponential backoff. Ensure your webhook handlers are idempotent by id and type.

5. Error handling

All errors follow a consistent structure:

{
  "error": {
    "code": "AP700",
    "message": "Missing required Parameter: 'merchantId'",
    "help": "Contact [email protected] for help."
  }
}

Common error codes:

  • missing_required_pii: Required user PII not provided for rtp=true.
  • eligibility_failed: Routing number not RFP-enabled or offline.
  • feature_disabled: Instant Pay In not enabled for this merchant.
  • invalid_request: Schema or type errors.
  • provider_error: Upstream provider returned an error; check details.

6. UX & User Engagement

Because the RfP flow requires the user to authorize the payment in their banking app, it is important to ensure proper messaging and notifications to keep the user engaged throughout the journey.

Pending RfP CTA

In order to ensure the user takes action on a pending RfP transaction, once you receive the transaction_rfp_initiated webhook topic, your UI should display a call to action that the user needs to complete the payment authorization before proceeding. Your application should disallow other actions until the payment is authorized.

Below is an example of what your UI can display.

Re-engagement CTA

In order to re-engage the user after an RfP transaction has been completed, you should send a notification to the user upon receiving the transaction_completed webhook in order to prompt the user to come back to your application.

This notification can be in the form of native push notification, SMS, or email.


7. Best practices

  • Backend

    • Include rtp flag when creating transactions via POST /transactions.
    • Store and handle webhook events for transaction and payment lifecycles.
    • Ensure idempotency for both transaction creation and webhook handling.
    • Store user information during KYC process to ensure necessary PII can be passed via API without adding friction to UX.
  • Frontend / Product

    • Clearly communicate when a payment is instant and irrevocable.
    • Send push notifications based on webhook events to help users return to originating app.
  • Monitoring

    • Track rates for:
      • Eligibility (RFP available vs fallback to ACH).
      • RFP delivery success.
      • User approvals vs rejections vs expirations.
      • RTP payout success vs failures.