> This is Payabli documentation. For a complete page index, fetch https://docs.payabli.com/llms.txt — append .md to any page URL for lightweight markdown. For section-level indexes, query parameters, and other AI-optimized access methods, see https://docs.payabli.com/ai-agents.md

# Accept IVR payments (API)

> Integrate IVR card and ACH payments step by step

<AudienceInfo audiences={['developers']} />

This guide walks through building an Interactive Voice Response (IVR) call tree that accepts card and ACH payments through Payabli's API. For how IVR payments work, supported payment types, and Nacha compliance requirements, see the [IVR payments overview](/guides/pay-in-ivr-overview).

## Prerequisites

Before you start building, confirm the following:

* **Active Payabli paypoint** with card or ACH processing enabled.
* **Payabli API key** scoped to match your call tree:
  * Use an **org-level or sub-org-level** key if the call tree serves payors from any paypoint under that org or sub-org.
  * Use a **paypoint-level** key if the call tree is dedicated to a specific paypoint.
* **Payor lookup capability on your platform.** Your platform's API must identify payors by one or more parameters (phone number, account number, ZIP code, or a combination).

You can complete the integration in three steps:

1. Provision a PCI-compliant phone number in SharpenCX.
2. Build and test your call tree in SharpenCX/Plum against the Payabli sandbox.
3. Deploy to production using SharpenCX/Plum deployment variables.

## Step 1: Provision a PCI-compliant phone number

All IVR payments must be processed through a PCI DSS-compliant phone number provisioned in SharpenCX. This keeps dual-tone multi-frequency (DTMF) keypad input containing sensitive cardholder data inside a compliant environment and away from your platform servers.

Log in to your SharpenCX account and follow their process to obtain a PCI-compliant phone number. Use this number as your development and test number throughout the build phase.

<Info>
  Your development phone number can later become your production number. You don't need to provision a separate number for production unless you want one. See [Step 3](#step-3-deploy-to-production) for both deployment options.
</Info>

## Step 2: Build and test your call tree in sandbox

Build your call flow in SharpenCX using whichever approach fits your team best: [Plum Fuse](https://docs.plumvoice.com/fuse), [Plum VoiceXML](https://docs.plumvoice.com/dev/developer-reference/tutorial), or the [Plum Dev APIs](https://docs.plumvoice.com/dev/plum-dev-apis/dev-outbound-apis).

Whichever approach you use, configure all REST API calls in the call tree to use **sandbox endpoints** during the build phase: your platform's sandbox API and the Payabli sandbox at `api-sandbox.payabli.com`. This lets you process test transactions with test data before going live.

### Get your Payabli sandbox API credentials

In your Payabli sandbox environment, locate the API key that matches the scope of your call tree:

* **Org-level or sub-org-level API key** if the call tree serves payors across multiple paypoints. The `entryPoint` must be returned dynamically by your platform's account lookup and passed in the `/v2/MoneyIn/getpaid` request body.
* **Paypoint-level API key** if the call tree is dedicated to a specific paypoint. The `entryPoint` is already known and can be set directly in the call tree.

<Warning>
  Configure your SharpenCX integration to pass the API key as an HTTP `requestToken` header, not as a query parameter or in the request body. This prevents credentials from appearing in call tree logs or debug output. Plum Fuse also lets you mark any call tree step as **Private** so none of its data is written to call logs. Use this on any step that handles sensitive values.
</Warning>

### Design the call flow

Use the typical [IVR payment flow](/guides/pay-in-ivr-overview#how-ivr-payments-work) in the overview as your starting structure. Your specific call tree design will vary based on your use case, what your account lookup returns, and whether you're offering card, ACH, or both.

### Configure the Payabli API call

Submit the transaction to the [Make a transaction (v2)](/developers/api-reference/moneyinV2/make-a-transaction) endpoint at `/v2/MoneyIn/getpaid`.

Follow the same request patterns you'd use for standard payment processing. The only IVR-specific addition is `"source": "ivr"` in the request body. Include `serviceFee` if your paypoint uses service fees, and populate `customerData` with standard payor details.

<Note>
  **This guide uses Payabli's v2 transaction APIs.** v2 returns the unified response format and is the recommended path for new integrations. If you're already running on v1 (`/moneyin/getpaid`), the request body is identical, so the examples below still apply. Only the endpoint URL and the response shape differ. For the v1 response structure, see the [v1 Make a transaction reference](/developers/api-reference/moneyin/make-a-transaction). For background on v2 and the unified response format, see [Pay In transaction APIs v2](/developers/api-reference/moneyIn/v2) and the [Pay In unified response codes reference](/guides/pay-in-unified-response-codes-reference).
</Note>

<Info>
  For org or sub-org level call trees, substitute `entryPoint` dynamically from your platform's account lookup response. For paypoint-level call trees, set `entryPoint` directly in the call tree configuration.
</Info>

#### Example: New card payment

```json
{
  "source": "ivr",
  "entryPoint": "<ENTRY_POINT>",
  "paymentDetails": {
    "totalAmount": 150.00,
    "serviceFee": 3.75
  },
  "paymentMethod": {
    "method": "card",
    "cardnumber": "<DTMF_COLLECTED_PAN>",
    "cardexp": "<DTMF_COLLECTED_EXPIRY>",
    "cardcvv": "<DTMF_COLLECTED_CVV>",
    "cardzip": "<DTMF_COLLECTED_ZIP>",
    "cardHolder": "<PAYOR_NAME>",
    "initiator": "payor"
  },
  "customerData": {
    "firstName": "<PAYOR_FIRST_NAME>",
    "lastName": "<PAYOR_LAST_NAME>",
    "billingEmail": "<PAYOR_EMAIL>",
    "billingPhone": "<PAYOR_PHONE>",
    "customerId": "<PAYOR_CUSTOMER_ID>",
    "customerNumber": "<PAYOR_CUSTOMER_NUMBER>"
  }
}
```

#### Example: New ACH payment

Set `achCode` based on what the caller indicated for account holder type: `TEL` for personal, `CCD` for business.

```json
{
  "source": "ivr",
  "entryPoint": "<ENTRY_POINT>",
  "paymentDetails": {
    "totalAmount": 150.00,
    "serviceFee": 0
  },
  "paymentMethod": {
    "method": "ach",
    "achCode": "TEL",
    "achHolder": "<PAYOR_NAME>",
    "achHolderType": "<DTMF_COLLECTED_HOLDER_TYPE>",
    "achAccountType": "<DTMF_COLLECTED_ACCOUNT_TYPE>",
    "achRouting": "<DTMF_COLLECTED_ROUTING>",
    "achAccount": "<DTMF_COLLECTED_ACCOUNT>"
  },
  "customerData": {
    "firstName": "<PAYOR_FIRST_NAME>",
    "lastName": "<PAYOR_LAST_NAME>",
    "billingEmail": "<PAYOR_EMAIL>",
    "billingPhone": "<PAYOR_PHONE>",
    "customerId": "<PAYOR_CUSTOMER_ID>",
    "customerNumber": "<PAYOR_CUSTOMER_NUMBER>"
  }
}
```

<Warning>
  Before implementing ACH IVR, coordinate with your Partner Success Manager to schedule a discovery call with Payabli. Your call tree must include a verbal authorization prompt and trigger a written email or SMS confirmation after payment. Your platform must also retain IVR call logs as dispute evidence. See [Nacha compliance for ACH IVR](/guides/pay-in-ivr-overview#nacha-compliance-for-ach-ivr) in the overview for the full requirements.
</Warning>

#### Example: Saved payment method

Use this when the caller selects a saved card or saved ACH returned by your account lookup. Set `method` to match the saved payment method type (`card` or `ach`) and submit the Payabli token ID in `storedMethodId`. For saved ACH, you don't need to send `achCode`: Payabli retrieves it from the saved payment method record.

```json
{
  "source": "ivr",
  "entryPoint": "<ENTRY_POINT>",
  "paymentDetails": {
    "totalAmount": 150.00,
    "serviceFee": 0
  },
  "paymentMethod": {
    "method": "<card_or_ach>",
    "storedMethodId": "<PAYABLI_TOKEN_ID>",
    "storedMethodUsageType": "unscheduled",
    "initiator": "payor"
  },
  "customerData": {
    "firstName": "<PAYOR_FIRST_NAME>",
    "lastName": "<PAYOR_LAST_NAME>",
    "billingEmail": "<PAYOR_EMAIL>",
    "billingPhone": "<PAYOR_PHONE>",
    "customerId": "<PAYOR_CUSTOMER_ID>",
    "customerNumber": "<PAYOR_CUSTOMER_NUMBER>"
  }
}
```

### Handle the API response

Payabli returns a JSON response immediately after processing. Your call tree should evaluate the response and use text-to-speech to read a confirmation or error message back to the caller.

The fields below describe the **v2** response (unified response format). If you're still on v1, see [v1 response fields](#v1-response-fields) further down for the equivalent mapping.

| v2 response field     | Description                                                                               | Suggested TTS handling                                                       |
| --------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `code`                | Unified response code. `A0000` means approved. Codes starting with `D` indicate declines. | Branch on this field to route to success or failure handling.                |
| `reason`              | Short human-readable result (for example, `Approved` or `Declined`).                      | Read directly or map to a custom caller message.                             |
| `explanation`         | Longer human-readable detail about the result.                                            | Use for detailed error messaging.                                            |
| `action`              | Recommended next action for the caller.                                                   | Use to build decline-recovery prompts (for example, "Try a different card"). |
| `data.paymentTransId` | Unique transaction ID (for example, `3040-96dfa9a7c4ed4f82a3dd4a4a12ad28ae`).             | Read the first 10 characters to the caller as a confirmation number.         |
| `data.transStatus`    | Numeric transaction status. `1` indicates the transaction was processed.                  | Use alongside `code` for status branching.                                   |

For the full list of v2 codes, see [Pay In unified response codes reference](/guides/pay-in-unified-response-codes-reference).

#### v1 response fields

If your integration targets `/moneyin/getpaid` (v1), use these fields instead. The caller-facing logic is the same — only the field names change.

| v1 response field          | Equivalent v2 field                                                   | Description                                                                   |
| -------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `isSuccess`                | Top-level status is in `code` (prefix `A` = approved, `D` = declined) | Boolean: `true` if the API call itself succeeded.                             |
| `responseData.referenceId` | `data.paymentTransId`                                                 | Unique transaction ID. Read the first 10 characters as a confirmation number. |
| `responseData.resultCode`  | `code`                                                                | `1` means approved. Other values indicate decline or error.                   |
| `responseData.resultText`  | `reason`                                                              | Human-readable status (`Approved`, `Declined`).                               |

See the [v1 Make a transaction reference](/developers/api-reference/moneyin/make-a-transaction) for the full v1 response schema.

### Test with sandbox credentials

Before moving to production, test your call tree end-to-end using the Payabli sandbox:

* Use [Payabli sandbox test card numbers](/guides/test-accounts-reference#cards) for card transactions.
* Use any valid routing number and any test account number to simulate ACH transactions.
* For ACH, verify that the correct `achCode` is sent based on `achHolderType`: `TEL` for personal, `CCD` for business.
* For saved payment method flows, verify the Payabli token ID is correctly retrieved from your account lookup and submitted in the request.
* Verify that `"source": "ivr"` appears on all transactions in the Payabli sandbox.
* Confirm your call tree's success and failure branches work correctly for each payment type.
* For ACH, verify that a confirmation email or SMS is triggered to the caller after a successful payment.

## Step 3: Deploy to production

Once your sandbox testing is complete, you're ready to go live. If you're using Plum Fuse, the deployment process lets you define deployment variables that override sandbox values with production credentials, so you don't have to edit or duplicate your call tree.

### Set up deployment variables

In Plum Fuse, deployment variables override specific sandbox values with production equivalents at deploy time. You don't need to touch the call tree logic itself, only the variables that change for production.

Variables to override with production values:

* **Payabli API key.** Swap the sandbox key for the production key.
* **Payabli endpoint host.** Swap `api-sandbox.payabli.com` for `api.payabli.com`.
* **`entryPoint`.** If set directly (paypoint-level call trees), swap the sandbox value for the production value.
* **Partner platform API keys.** Swap sandbox credentials for production credentials.
* **Partner platform API endpoints.** Swap sandbox URLs for production URLs.

<Info>
  Using deployment variables means your sandbox and production environments share the same call tree logic. Only the variable values differ. This keeps maintenance simple and reduces the risk of divergence between your test and live configurations.
</Info>

### Choose a phone number deployment option

You have two options for the production phone number:

* **Reuse your test number (recommended).** Deploy the production call tree to the same PCI-compliant phone number you used for testing. That number immediately begins handling live traffic. This is the fastest path to go-live and doesn't require provisioning another number.
* **Provision a new production number.** Procure one or more new PCI-compliant phone numbers from SharpenCX and deploy the production call tree to those. Your original test number stays available for ongoing regression testing, while the new number handles live traffic.

Once the production call tree is deployed and your phone number is receiving live traffic, your IVR payment integration is complete. Transactions appear in Payabli tagged with `source: ivr` for reporting and reconciliation.

## Related resources

See these related resources to help you get the most out of Payabli.

<AccordionGroup>
  <Accordion title="Prerequisites">
    * **[IVR payments overview](/guides/pay-in-ivr-overview)** - Start with the overview for how IVR payments work and what payment types are supported
  </Accordion>

  <Accordion title="References">
    * **[Pay In schemas](/guides/pay-in-schemas-overview)** - Learn about Pay In (money in) transaction schemas
    * **[Pay In statuses](/guides/pay-in-status-reference)** - Learn about Pay In (money in) statuses
    * **[Pay In unified response codes reference](/guides/pay-in-unified-response-codes-reference)** - Complete reference for Payabli's unified Pay In transaction response codes
    * **[Payment methods for testing](/guides/test-accounts-reference)** - Use these payment methods to test your integration with Payabli
  </Accordion>

  <Accordion title="Related topics">
    * **[Make a sale transaction with the API](/guides/pay-in-developer-transactions-create)** - Learn how to authorize and capture a sales transaction in one step using the API
    * **[Make a sale with a saved payment method](/guides/pay-in-developer-transactions-card-on-file)** - Learn how to make a sale transaction via the API with a customer's saved payment method
    * **[ACH payments cycle](/guides/pay-in-ach-cycle-overview)** - Learn how ACH transactions work
    * **[Make a transaction API (v2)](/developers/api-reference/moneyinV2/make-a-transaction)** - API reference for the /v2/MoneyIn/getpaid endpoint used by IVR call trees
  </Accordion>
</AccordionGroup>