# Telnyx IVR integration guide

> This guide covers how to integrate Shuttle payments into your Telnyx IVR workflows using the <Pay> TeXML verb for self-service payment collection. Telnyx sends

This guide covers how to integrate Shuttle payments into your Telnyx IVR workflows using the `<Pay>` TeXML verb for self-service payment collection. Telnyx sends the payment details straight to the Shuttle Pay connector, so you do not need a Twilio account to take payments on Telnyx calls.

## Overview

The [Telnyx `<Pay>` verb](https://developers.telnyx.com/docs/voice/programmable-voice/pay) collects payment details during a phone call. Customers enter their card or bank details using their phone keypad in response to voice prompts, without agent involvement. Telnyx passes the details to Shuttle, which charges or tokenises them through your payment gateway. Your application receives the result, never the card details.

Telnyx implements the same pay connector contract as Twilio, so the [Shuttle Pay connector reference](/platforms/twilio/shuttle-connector-reference/) applies, with two differences:

- **Currency.** Telnyx always sends USD to the connector. Pass the real currency as `<Parameter name="currency">` on every charge.
- **Results.** Telnyx does not pass Shuttle's response back to your `action` URL. Read the result from Shuttle with the [result lookup](#looking-up-the-result).

## Prerequisites

- A Telnyx account with a TeXML application handling your calls
- A Shuttle instance key and secret key
- A payment gateway connected to your Shuttle instance
- An HTTPS endpoint you control that can return TeXML

## Step 1: Create the connector

1. Log into the [Telnyx Portal](https://portal.telnyx.com/)
2. In the Voice Suite, go to **Programmable Voice > Payment connectors**
3. Select **Create Connector**

![Telnyx Payment Connectors](/_astro/cdn/317251513e/telnyx_payment_connectors.png)

4. Enter these settings:

| Field | Value |
|-------|-------|
| Connector name | `shuttle-pay-connector` (or similar - note this for later use) |
| Mode | `test` until you are ready to take real payments |
| Endpoint URL | `https://app.shuttleglobal.com/c/api/telnyxpay` |
| Authentication type | `basic` |
| Username | Your Shuttle instance key |
| Password | Your Shuttle secret key |

> **The Endpoint URL is Shuttle, not your application.** Telnyx sends the collected details there and expects Shuttle's reply. If it points at your TeXML endpoint, or the username and password are not your instance key and secret key, every payment fails with `payment-connector-error` and no `PayErrorCode`.

When you are ready to take real payments, switch the connector to live from the toggle in the connector list.

![Switch to live mode](/_astro/cdn/4ea6d3258a/telnyx_switch_to_live.png)

> **A live connector cannot be edited, and cannot be switched back to test.** To change its endpoint or credentials, delete it and create it again. Test connectors can be edited.

You can also manage connectors through the [Telnyx API](https://developers.telnyx.com/docs/voice/programmable-voice/pay).

## Telnyx `<Pay>` verb

The `<Pay>` verb is added to your TeXML to collect payment details. See the [Telnyx Pay documentation](https://developers.telnyx.com/docs/voice/programmable-voice/pay) for the full reference.

**Telnyx parameters:**

| Parameter | Required | Description |
|-----------|----------|-------------|
| `paymentConnector` | Yes | Your connector name (e.g., `shuttle-pay-connector`) |
| `action` | Yes | URL for next TeXML instructions after payment |
| `method` | No | `POST` or `GET` for the `action` request (default: `POST`) |
| `chargeAmount` | No | Amount to charge, in major units - omit for tokenisation only |
| `transactionType` | No | `charge` or `tokenize` (default: inferred from `chargeAmount`) |
| `paymentMethod` | No | `credit-card` or `ach-debit` (default: `credit-card`) |
| `postalCode` | No | Collect a postal code for AVS (default: `true`) |
| `timeout` | No | Seconds allowed for each entry (default: `5`) - **we recommend `30`** |
| `maxAttempts` | No | Attempts allowed for each entry, 1-3 (default: `1`) - **we recommend `3`** |
| `statusCallback` | No | URL for progress updates |
| `description` | No | Transaction description |

> **Set `timeout="30"` and `maxAttempts="3"`.** Telnyx's defaults give the caller five seconds and one attempt per entry, which is too short to key a card number by hand.

Telnyx's own `currency` attribute only accepts `USD`, so Shuttle ignores it. Use the `currency` parameter below instead.

**Transaction types:**

| Type | chargeAmount | Shuttle `action` | Result |
|------|--------------|------------------|--------|
| Tokenisation | Omit | - | Card saved, no charge |
| Payment (capture) | Set amount | `PAYMENT` | Card charged immediately |
| Payment (auth only) | Set amount | `AUTH` | Funds held, capture later |
| Payment + save card | Set amount | Include `save_card=true` | Card charged and saved |

**Shuttle parameters** are passed as `<Parameter>` elements within the `<Pay>` verb. See the [Shuttle Pay connector reference](/platforms/twilio/shuttle-connector-reference/#request-parameters) for the full list. These are the ones to get right on Telnyx:

| Parameter | Rule on Telnyx |
|-----------|----------------|
| `currency` | Required on every charge. The ISO currency code, e.g. `USD` or `GBP` |
| `bank_account_type` | Required for ACH: `checking`, `savings` or `businessChecking` |
| `AVSName` | Required for ACH: the account holder's name |
| `alt_key` | Recommended on every `<Pay>`: your own reference, unique per attempt, used to [look up the result](#looking-up-the-result) |

ACH works as on Twilio: always pass `bank_account_type` and `AVSName`, because neither carrier collects them. Use parameter names exactly as shown: most are case-sensitive. The only exceptions on Telnyx are `AVSName`, `currency` and `bank_account_type`, which are matched whatever their case.

A charge without `currency`, or an ACH payment without its account type or holder name, is rejected with `INVALID_REQUEST` before it reaches your gateway.

## Examples

### Card tokenisation

Save a card for future use without charging:

```xml
<Pay
  paymentConnector="shuttle-pay-connector"
  action="https://yourapp.com/next_step"
  timeout="30"
  maxAttempts="3">
  <Parameter name="alt_key" value="SAVE-12345-1"/>
</Pay>
```

### Standard card payment

```xml
<Pay
  paymentConnector="shuttle-pay-connector"
  action="https://yourapp.com/next_step"
  chargeAmount="29.99"
  postalCode="false"
  timeout="30"
  maxAttempts="3">
  <Parameter name="currency" value="USD"/>
  <Parameter name="save_card" value="true"/>
  <Parameter name="alt_key" value="INV-12345-1"/>
</Pay>
```

### Pre-authorisation

Hold funds for later capture:

```xml
<Pay
  paymentConnector="shuttle-pay-connector"
  action="https://yourapp.com/next_step"
  chargeAmount="99.99"
  timeout="30"
  maxAttempts="3">
  <Parameter name="currency" value="USD"/>
  <Parameter name="action" value="AUTH"/>
  <Parameter name="alt_key" value="AUTH-12345-1"/>
</Pay>
```

### ACH payment

```xml
<Pay
  paymentConnector="shuttle-pay-connector"
  action="https://yourapp.com/next_step"
  chargeAmount="49.99"
  paymentMethod="ach-debit"
  timeout="30"
  maxAttempts="3">
  <Parameter name="currency" value="USD"/>
  <Parameter name="bank_account_type" value="checking"/>
  <Parameter name="AVSName" value="John Smith"/>
  <Parameter name="alt_key" value="INV-12346-1"/>
</Pay>
```

See [ACH-specific parameters](/platforms/twilio/shuttle-connector-reference/#ach-specific) for the other bank details you can pass.

### Recurring payment

```xml
<Pay
  paymentConnector="shuttle-pay-connector"
  action="https://yourapp.com/next_step"
  chargeAmount="19.99"
  timeout="30"
  maxAttempts="3">
  <Parameter name="currency" value="USD"/>
  <Parameter name="frequency" value="MONTHLY"/>
  <Parameter name="occurrences" value="12"/>
  <Parameter name="start_date" value="2027-01-01T00:00:00"/>
  <Parameter name="alt_key" value="SUB-12345-1"/>
</Pay>
```

### With full traceability

```xml
<Pay
  paymentConnector="shuttle-pay-connector"
  action="https://yourapp.com/payment_complete"
  chargeAmount="149.99"
  description="Premium Plan - Monthly"
  timeout="30"
  maxAttempts="3">
  <Parameter name="currency" value="USD"/>
  <Parameter name="alt_key" value="SUB-2024-001"/>
  <Parameter name="account_crm_key" value="cus_123456"/>
  <Parameter name="account_first_name" value="Jane"/>
  <Parameter name="account_last_name" value="Doe"/>
  <Parameter name="account_email" value="jane.doe@example.com"/>
  <Parameter name="metadata_plan" value="premium"/>
  <Parameter name="metadata_billing_cycle" value="monthly"/>
</Pay>
```

### With billing address (AVS)

Pass the cardholder's billing address with `billing_*` parameters if your payment gateway performs address verification. The `account_*` parameters carry customer identity only - they do not include an address. See the [Shuttle Pay connector reference](/platforms/twilio/shuttle-connector-reference/) for the full field list.

```xml
<Pay
  paymentConnector="shuttle-pay-connector"
  action="https://yourapp.com/payment_complete"
  chargeAmount="49.99"
  timeout="30"
  maxAttempts="3">
  <Parameter name="currency" value="USD"/>
  <Parameter name="account_first_name" value="Jane"/>
  <Parameter name="account_last_name" value="Doe"/>
  <Parameter name="billing_line1" value="123 Main Street"/>
  <Parameter name="billing_line2" value="Apt 4B"/>
  <Parameter name="billing_line4" value="Springfield"/>
  <Parameter name="billing_line5" value="62704"/>
  <Parameter name="billing_line6" value="IL"/>
  <Parameter name="billing_country" value="US"/>
</Pay>
```

### Customising voice prompts

Customise the messages spoken to callers using the `<Prompt>` element, with the text in a nested `<Say>`. The `for` attribute names the step: `payment-card-number`, `expiration-date`, `postal-code`, `security-code`, `bank-routing-number` or `bank-account-number`. See the [Telnyx Pay documentation](https://developers.telnyx.com/docs/voice/programmable-voice/pay) for prompts that vary by attempt, error or card type.

```xml
<Pay
  paymentConnector="shuttle-pay-connector"
  action="https://yourapp.com/next_step"
  chargeAmount="29.99"
  timeout="30"
  maxAttempts="3">
  <Parameter name="currency" value="USD"/>
  <Prompt for="payment-card-number">
    <Say>Please enter your card number using your phone keypad</Say>
  </Prompt>
  <Prompt for="expiration-date">
    <Say>Now enter the expiration date</Say>
  </Prompt>
  <Prompt for="security-code">
    <Say>Finally, enter the three digit security code</Say>
  </Prompt>
</Pay>
```

## Handling responses

Telnyx sends a request to your `action` URL when `<Pay>` completes. It reports Telnyx's own outcome, but it does not pass Shuttle's response through: there are no `PayConnector_` fields, and on a decline Telnyx drops Shuttle's payment ID and decline message.

**Telnyx fields:**

| Field | Description |
|-------|-------------|
| `PayResult` | `success` or `failed` |
| `Result` | Detailed outcome, e.g. `success`, `payment-connector-error`, `validation-error`, `too-many-failed-attempts`, `cancelled` |
| `TransactionId` | Telnyx's ID for this payment - use it to [look up the result](#looking-up-the-result) |
| `ChargeId` / `PaymentConfirmationCode` | Shuttle payment ID (successful charges only) |
| `TokenId` / `PaymentToken` | Shuttle payment method ID (successful tokenisations only) |
| `PayErrorCode` | Shuttle's error code, when Shuttle declined or rejected the payment |
| `PaymentError` | Telnyx's description of the error - `connector_error` when Shuttle declined it |
| `PaymentCardNumber` | Masked card number |
| `PaymentCardType` | Card brand (visa, mastercard, etc.) |

> **Important:** Always look up the result before you tell the caller what happened. `success` only means Shuttle did not return an error: the payment can still be pending or under review.

**What the outcome means:**

| What you receive | Meaning | What to do |
|------------------|---------|------------|
| `PayResult=success` | Shuttle accepted the payment | Look up the result and check `status` |
| `Result=payment-connector-error` with a `PayErrorCode` | Shuttle declined or rejected the payment | Look up the result for the decline reason |
| `Result=payment-connector-error` without a `PayErrorCode` | Shuttle's answer did not reach Telnyx in time. The payment may have succeeded | Look up the result. If it is not found yet, wait a few seconds and try again. Never treat this as a failure without checking |
| Any other `Result` | The caller did not complete entry, so nothing was sent to Shuttle | Offer to try again |

## Looking up the result

Shuttle keeps its answer to every payment request Telnyx sends it, whether the payment succeeded or failed. Read it with the same instance key and secret key you gave the connector:

| Method | Path | Use |
|--------|------|-----|
| `GET` | `/c/api/telnyxpay/{TransactionId}` | When your `action` URL received `TransactionId` |
| `GET` | `/c/api/telnyxpay?alt_key={alt_key}` | By your own `alt_key`, e.g. when the `action` request never arrived |

```bash
curl https://app.shuttleglobal.com/c/api/telnyxpay/{TransactionId} \
  -u '{instance_key}:{secret_key}'

curl "https://app.shuttleglobal.com/c/api/telnyxpay?alt_key=INV-12345-1" \
  -u '{instance_key}:{secret_key}'
```

**Response fields:**

| Field | Description |
|-------|-------------|
| `transaction_id` | Telnyx's transaction ID |
| `alt_key` | Your `alt_key`, if you passed one |
| `connector` | `telnyxpay` |
| `request_type` | `PAYMENT` for a charge, `TOKENISE` for a tokenisation |
| `updated` | When Shuttle answered Telnyx |
| `status` | **Primary status - always check this.** See below |
| `error` | `{ "code", "message" }` - the decline or rejection Shuttle returned, including the gateway's message. `null` if there was none |
| `payment` | The [payment](/platforms/reference/operations/getpayment/) (charges only) |
| `contract` | The [contract](/platforms/reference/operations/getcontract/) behind the payment (charges only) |
| `payment_method` | The [payment method](/platforms/reference/operations/getpaymentmethod/) |
| `charge` | The first [charge](/platforms/reference/operations/getcharge/) (recurring charges only) |
| `transaction` | The first [transaction](/platforms/reference/operations/gettransaction/) (recurring charges only) |

Card and bank account numbers and security codes are removed.

**Status values:**

| `status` | Meaning |
|----------|---------|
| `SUCCESS`, `PENDING`, `UNRESOLVED`, `DECLINED`, `REQAUTH` | A charge: the payment status, as in [Payment status values](/platforms/twilio/shuttle-connector-reference/#payment-status-values) |
| `ACTIVE` | A tokenisation: the card or account was saved |
| `INACTIVE` | A tokenisation: the card or account was not saved - see `error` |
| `ERROR` | Shuttle could not create a payment or payment method, e.g. a missing `currency` parameter or a gateway error - see `error` |

For a recurring payment that starts on a future date, `status` is the contract's status until the first charge is taken.

### Example: successful card payment

```json
{
  "transaction_id": "a1b2c3d4-5e6f-7a8b-9c0d-e1f2a3b4c5d6",
  "alt_key": "INV-12345-1",
  "connector": "telnyxpay",
  "request_type": "PAYMENT",
  "updated": "2026-10-12T14:26:53.137Z",
  "status": "SUCCESS",
  "error": null,
  "payment": { "id": "pay_20648_10045", "status": "SUCCESS", "gateway_status": "APPROVED", ... },
  "contract": { "id": "co_20648_10045", "status": "COMPLETED", ... },
  "payment_method": { "id": "pm_20648_10045", "status": "SINGLEUSE", ... }
}
```

The payment method is `SINGLEUSE` because the card was not saved. With `save_card=true` it is `ACTIVE`.

### Example: declined card payment

```json
{
  "transaction_id": "b2c3d4e5-6f7a-8b9c-0d1e-f2a3b4c5d6e7",
  "alt_key": "INV-12345-2",
  "connector": "telnyxpay",
  "request_type": "PAYMENT",
  "updated": "2026-10-12T14:31:08.512Z",
  "status": "DECLINED",
  "error": {
    "code": "DECLINED",
    "message": "Invalid CVC"
  },
  "payment": {
    "id": "pay_20648_10046",
    "status": "DECLINED",
    "gateway_status": "DECLINED",
    "gateway_message": "Invalid CVC",
    ...
  },
  "contract": { "id": "co_20648_10046", ... },
  "payment_method": { "id": "pm_20648_10046", ... }
}
```

### Example: successful tokenisation

```json
{
  "transaction_id": "c3d4e5f6-7a8b-9c0d-1e2f-a3b4c5d6e7f8",
  "alt_key": "SAVE-12345-1",
  "connector": "telnyxpay",
  "request_type": "TOKENISE",
  "updated": "2026-10-12T14:35:41.008Z",
  "status": "ACTIVE",
  "error": null,
  "payment_method": { "id": "pm_20648_10047", "status": "ACTIVE", ... }
}
```

**Errors:**

| Status | Meaning |
|--------|---------|
| `404` | No result found. Nothing reached Shuttle for that ID, it is more than 24 hours old, or Shuttle is still processing the payment - try again after a few seconds |
| `401` | The credentials do not belong to the instance that took the payment |
| `400` | Neither a transaction ID nor an `alt_key` was given |

**Good to know:**

- **Results are kept for 24 hours.** After that, [retrieve the payment](/platforms/reference/operations/getpayment/) or [payment method](/platforms/reference/operations/getpaymentmethod/) by ID.
- **Make `alt_key` unique per attempt**, e.g. `ORDER-123-1`, then `ORDER-123-2` if the caller tries again. If you reuse an `alt_key`, the lookup returns the most recent attempt only. It finds tokenisations as well as charges.

## Shuttle webhooks

Shuttle webhooks are sent from Shuttle to your backend, separately from Telnyx's callbacks, so you keep a record of every payment even when a call drops part way through. See the [Shuttle Pay connector reference](/platforms/twilio/shuttle-connector-reference/#webhooks) for events and configuration.

## Post-payment operations

After a successful payment, use the Shuttle API for refunds, captures, voids and charging saved cards. See the [Shuttle Pay connector reference](/platforms/twilio/shuttle-connector-reference/#shuttle-api).

## Testing

New connectors start in test mode. A test connector still sends payments to Shuttle, but Telnyx only accepts its own test card numbers, such as `4111111111111111` (Visa), `5555555555554444` (Mastercard) and `378282246310005` (American Express) - any other card is rejected as `invalid-card-number`.

Point a test connector at a Shuttle sandbox instance with a [Sandbox Gateway](/platforms/testing/sandbox-gateway/), so no real payment is taken. All three cards above are Sandbox Gateway test cards, approved with any expiry date that hasn't passed and a 3-digit security code (4 digits for American Express).

Telnyx rejects the Sandbox Gateway's decline cards, so test declines with the security code instead: on the Visa or Mastercard card, a security code from `901` to `906` returns a decline or an unresolved payment, and `900` a pending one. See [CVC triggers](/platforms/testing/sandbox-gateway/#cvc-triggers). The triggers don't apply to American Express.

For ACH, Telnyx's test connector accepts any routing and account number of a valid length, but the Sandbox Gateway only accepts its own [test ACH accounts](/platforms/testing/sandbox-gateway/#test-ach-accounts), in USD: use routing number `110000000` with account number `000123456789` for an approval. Any other account is declined.

Before you go live, run a card charge, an ACH charge, a tokenisation and a decline end to end, and check the lookup returns each one. If you record calls, also retrieve the recording of a test payment and confirm the caller's key presses are not in it.

## Support

Shuttle supports the Shuttle Pay connector and the payment - declines, gateway responses, tokenisation, webhooks and the Shuttle API. When raising a payment with us, include the `TransactionId` or your `alt_key`. For TeXML, `<Pay>`, the Telnyx Portal, DTMF and call recording, contact [Telnyx support](https://support.telnyx.com).

## References

- [Telnyx Pay documentation](https://developers.telnyx.com/docs/voice/programmable-voice/pay) - Full `<Pay>` verb and connector reference
- [Shuttle Pay connector reference](/platforms/twilio/shuttle-connector-reference/) - Parameters, statuses, webhooks, API operations
- [Twilio IVR integration guide](/platforms/twilio/integration-guide/) - The same integration on Twilio
- [Sandbox Gateway](/platforms/testing/sandbox-gateway/) - Test card numbers