Skip to navigation

Pay

View as MarkdownOpen in Claude

Overview

The <Pay> verb enables secure payment processing during voice calls. When implemented in your voice application, it handles the complete payment flow including data collection, validation, and processing through your configured payment gateway.

The <Pay> verb also has two nouns that are embedded within it:

  • <Parameter>: Pass custom parameters to your payment processor.
  • <Prompt>: Customize the prompts used during payment collection.

Core functionality

  • Secure payment information collection
  • Real-time payment processing
  • Payment method tokenization
  • Multi-currency support
  • Automated retry handling
  • Status tracking via webhooks

Transaction types

The <Pay> verb supports two primary transaction types: immediate charges and tokenization.

Immediate charges

When you need to process a payment right away, use an immediate charge transaction. This collects the payment details and processes the transaction in one step.

To execute an immediate charge:

<Pay chargeAmount="25.00"/>

Setting any positive value for chargeAmount initiates an immediate charge transaction.

Tokenization

Tokenization allows you to securely store payment information for future use. Instead of processing a payment, it generates a secure token that represents the payment method. This is particularly useful for:

  • Subscription services
  • Recurring billing

To tokenize payment information:

<Pay chargeAmount="0"/>

You can also omit the chargeAmount attribute entirely.

Your payment connector creates the token with your payment processor and returns it as token_id. SignalWire passes it to your action URL as PaymentToken, and you can use it for future transactions without asking the customer to re-enter their payment details. See the tokenization example for the full flow.


Attributes

paymentConnectorUrl
stringRequired

The HTTPS URL of your payment connector. SignalWire sends it a POST request with the collected payment details. See paymentConnectorUrl request parameters and securing your payment connector.

action
string

HTTPS endpoint that receives POST requests after payment completion. Must be HTTPS. Response determines next cXML instructions. For tokenization, receives the token as PaymentToken. For charges, receives PaymentConfirmationCode. Defaults to the current cXML document URL.

chargeAmount
decimal

Amount to charge. Set to 0 or omit for tokenization only. Must be between 0 and 1,000,000.

currency
stringDefaults to usd

Three-letter currency code (e.g., usd, eur, gbp). Must be supported by selected payment connector. Currently only usd is accepted.

description
string

Transaction description passed to payment processor. Appears in transaction records.

input
stringDefaults to dtmf

The only supported input method for payment data collection. All captured digits are automatically redacted in logs.

language
stringDefaults to en-us

Controls prompt language. Supports en-AU, en-CA, en-GB, en-IN, en-US for all payments. Additional es-ES, es-MX, fr-CA, fr-FR, de-DE, it-IT for credit card only. See language list.

maxAttempts
integerDefaults to 1

Maximum number of retry attempts when timeout occurs or invalid data is received. Allowed values are 1, 2, or 3. Payment flow terminates after maxAttempts is reached.

minPostalCodeLength
integerDefaults to 0

Minimum required length for postal code input. Used for input validation.

paymentMethod
stringDefaults to credit-card

Determines payment collection flow. Credit card flow captures card number, expiration, security code, and postal code. ACH flow captures routing and account numbers.

postalCode
string | booleanDefaults to true

Controls postal code collection. When false, skips collection for postal code. When true, prompts the user for collection. When a String value, uses provided value without needing to collect the postal code from the user.

securityCode
booleanDefaults to true

Controls whether CVV/security code collection is required. When false, skips security code collection.

statusCallback
string

HTTPS endpoint that receives real-time payment status updates via POST requests. Includes details about current payment stage, errors, and attempt counts. See Status Callback Parameters for more details.

timeout
integerDefaults to 5

Number of seconds to wait for the next digit input before timing out. Must be a positive integer greater than 3 seconds. Applies to all input collection stages.

tokenType
stringDefaults to reusable

Determines token persistence. Allowed values are one-time or reusable. One-time for single use, reusable for recurring use. Payment-method specific to certain processors.

validCardTypes
stringDefaults to visa mastercard amex

Space-separated list of accepted card types. Validates card number against specified types. Allowed values: visa, mastercard, amex, maestro, discover, optima, jcb, diners-club, enroute.

Webhook request parameters

paymentConnectorUrl request parameters

When all payment details have been collected from the caller, SignalWire sends a POST request with a JSON body to your paymentConnectorUrl. Charges and tokenization send the same fields. SignalWire signs the request, so see securing your payment connector to verify it.

Response from paymentConnectorUrl

A valid response from your paymentConnectorUrl must be provided to indicate the success or failure of the transaction. More information can be found in the Webhook Response section.

transaction_id
string

Unique identifier for the transaction.

method
string

Payment method (credit-card).

cardnumber
string

Customer’s full credit card number.

cvv
string

Card security code.

postal_code
string

Billing postal code.

description
string

Transaction description.

chargeAmount
string

Amount to charge, from the chargeAmount attribute. 0 for tokenization, or 0.00 when the attribute is omitted.

token_type
string

Token persistence (reusable or one-time), from the tokenType attribute.

expiry_month
string

Card expiration month (2 digits).

expiry_year
string

Card expiration year (2 digits).

currency_code
string

Three-letter currency code, from the currency attribute.

parameters
object[]

Custom parameters from the <Parameter> nouns, as an array with one single-key object per parameter. Omitted when <Pay> has no <Parameter>.

Example

{
"transaction_id": "8c9d14d5-52ae-4e2e-b880-a14e6e1cda7d",
"method": "credit-card",
"cardnumber": "4242424242424242",
"cvv": "123",
"postal_code": "12345",
"description": "renew plan",
"chargeAmount": "123.45",
"token_type": "reusable",
"expiry_month": "01",
"expiry_year": "38",
"currency_code": "usd",
"parameters": [
{ "custom1": "value1" }
]
}

action request parameters

When a transaction is completed, SignalWire will POST to your action URL. The body of the request will contain the standard request parameters as well as the following parameters:

Response from action URL

A valid cXML response must be provided if the action URL is provided. More information can be found in the Webhook Response section.

BankAccountNumber
string

Masked bank account number. ACH payments only.

BankAccountType
string

Bank account type. ACH payments only.

BankRoutingNumber
string

Bank routing number. ACH payments only.

PaymentCardExpirationDate
string

Card expiration in MMYY format.

PaymentCardNumber
string

Masked card number (last 4 digits visible).

PaymentCardPostalCode
string

Postal code provided.

PaymentCardSecurityCode
string

Masked security code.

PaymentCardType
string

Card brand (visa, mastercard, etc.).

PaymentConfirmationCode
string

The charge_id your payment connector returned for a successful charge.

PaymentConnectorError
string

The error_message your payment connector returned.

PaymentConnectorErrorCode
string

The error_code your payment connector returned.

PaymentError
string

Detailed error information.

PaymentErrorCode
string

Numeric error reference.

PaymentMethod
string

Type of payment method used.

PaymentToken
string

The token_id your payment connector returned for a successful tokenization. Otherwise, the token type (reusable or one-time) from the tokenType attribute.

PayResult
string

Final outcome of the payment attempt. The possible values can be seen in the Result Values table.

Result values

ValueDescription
successPayment processed successfully
too-many-failed-attemptsMaximum retry attempts reached
payment-connector-errorGateway communication failure
caller-interrupted-with-starUser canceled with * key
caller-hung-upCall terminated by user
validation-errorInvalid parameter provided
internal-errorSystem processing error

statusCallback request parameters

When a status change occurs, the following parameters are sent to your statusCallback URL:

Attempt
integer

Current attempt number.

ErrorType
string

Specifies the nature of the error encountered (if any). Refer to the table below for detailed error type descriptions. The possible values can be seen in the Error Types table.

ExpirationDate
string

Card expiration date.

For
string

Indicates the present phase of the <Pay> request. The table below outlines the potential values. Possible values can be seen in the For Values table.

PaymentCardNumber
string

Masked card number.

PaymentCardPostalCode
string

Postal code provided.

PaymentCardType
string

Type of card provided.

PaymentMethod
string

Type of payment being processed.

SecurityCode
string

Masked security code.

For values

ValueDescription
payment-card-numberRequesting the customer’s credit or debit card details
expiration-dateRequesting the expiration date of the customer’s payment card
security-codeRequesting the security code (CVV) of the customer’s payment card
postal-codeRequesting the postal code linked to the customer’s payment card
bank-routing-numberRequesting the customer’s bank routing number
bank-account-numberRequesting the customer’s bank account number
payment-processingSending the payment details to your payment connector
payment-completedYour payment connector returned HTTP 200. This doesn’t mean the payment succeeded: a decline returned with HTTP 200 also sends payment-completed
payment-failedPayment details couldn’t be collected, or your payment connector didn’t return HTTP 200
payment-canceledThe caller pressed * to cancel

To find out whether the payment succeeded, check PayResult on the action request.

Error types

ErrorDescription
input-timeoutUser input timeout
invalid-card-numberFailed card validation
invalid-card-typeUnsupported card type
invalid-dateInvalid expiration date
invalid-security-codeInvalid CVV format
invalid-postal-codeInvalid postal code format
invalid-bank-routing-numberInvalid routing number
invalid-bank-account-numberInvalid account number
session-in-progressConcurrent session attempt

Webhook response

paymentConnectorUrl response

The response from your paymentConnectorUrl is used to inform if the transaction was successful or not.

Successful transaction

When a transaction is successful, the webhook should respond with one of the following formats (depending on the transaction type):

{
"charge_id":"charge_id",
"error_code":null,
"error_message":null
}

SignalWire passes charge_id to your action URL as PaymentConfirmationCode. When chargeAmount is zero or omitted, it passes token_id as PaymentToken.

Unsuccessful transaction

When a transaction is unsuccessful (declined by the payment processor), the webhook should respond with the following format:

{
"charge_id": null,
"error_code": "some error code",
"error_message": "some error message"
}

action response

When a transaction is completed, SignalWire will POST to your action URL. The response should return valid cXML for the next step in your application.

Example

<Response>
<Say>Thank you for your payment. Your transaction has been completed.</Say>
<Hangup/>
</Response>

Language support

The language attribute controls the language of automated prompts during payment collection.

You can customize prompt messages in any language using the <Prompt> noun, regardless of the selected language attribute.

Credit card payments

All credit card payments support the following languages:

English variants

  • en-AU (Australian English)
  • en-CA (Canadian English)
  • en-GB (British English)
  • en-IN (Indian English)
  • en-US (American English)

Additional languages

  • es-ES (European Spanish)
  • es-MX (Mexican Spanish)
  • fr-CA (Canadian French)
  • fr-FR (European French)
  • de-DE (German)
  • it-IT (Italian)

Examples

Simple payment collection:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please enter your payment information</Say>
<Pay chargeAmount="20.45"/>
</Response>

Payment with status tracking:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please enter your payment information</Say>
<Pay
chargeAmount="20.45"
action="https://your-callback-url.example.com/pay"
statusCallback="https://your-callback-url.example.com/status"
/>
</Response>

Basic tokenization:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please enter your card information to save for future use</Say>
<Pay
tokenType="reusable"
chargeAmount="0"
/>
</Response>

Tokenization with validation:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please enter your card information</Say>
<Pay
tokenType="reusable"
chargeAmount="0"
validCardTypes="visa mastercard"
securityCode="true"
postalCode="true"
/>
</Response>

Tokenize a card end to end

This <Pay> saves a card without charging it:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please enter your card information to save for future use</Say>
<Pay
chargeAmount="0"
tokenType="reusable"
paymentConnectorUrl="https://pay.example.com/connector"
action="https://example.com/pay-result"
/>
</Response>

SignalWire collects the card details and sends them to your payment connector with "chargeAmount": "0" and "token_type": "reusable". Your connector saves the card with your payment processor and replies with the processor’s token:

{
"token_id": "pm_1234567890",
"error_code": null,
"error_message": null
}

SignalWire then sends your action URL PayResult=success and PaymentToken=pm_1234567890. Store the token to charge the card later through your payment processor.

Custom retry logic:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please enter your payment information</Say>
<Pay
chargeAmount="75.00"
maxAttempts="3"
timeout="10"
/>
</Response>

International payment:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Por favor, ingrese su información de pago</Say>
<Pay
chargeAmount="100.00"
currency="mxn"
language="es-MX"
description="Mexican peso transaction"
/>
</Response>

Example with custom parameters and prompts

<Response>
<Pay chargeAmount="123.45" paymentConnectorUrl="https://example.com/accept-payment" description="renew plan">
<Parameter name="custom1" value="value1"/>
<Prompt for="payment-card-number" attempt="1"><Say>Let's collect your payment information. Please enter your credit card number</Say></Prompt>
<Prompt for="payment-card-number" attempt="2 3"><Say>Please enter your credit card number</Say></Prompt>
<Prompt for="security-code" cardType="amex"><Say>Please enter your credit card's security code. It's the 4 digits located on the front of your card. </Say></Prompt>
</Pay>
</Response>

Securing your payment connector

Requests to your paymentConnectorUrl carry the X-SignalWire-Signature and X-SignalWire-SHA256-Signature headers, keyed with your project’s Signing Key. They don’t come from a published IP range, so verify the signature and reject any request that doesn’t match. The connector request is signed like a JSON webhook, not like your action and statusCallback requests: X-SignalWire-Signature is the lowercase hex HMAC-SHA1 of the connector URL, with any credentials removed, followed by the raw JSON body. The form-parameter validation you use for other cXML webhooks won’t match it. SignalWire normalizes the URL before signing it: the scheme is lowercased, . and .. path segments are resolved, and a URL without a path gets /, so https://example.com is signed as https://example.com/.

For a second layer, put credentials in the connector URL. Credentials such as https://payuser:<LONG_RANDOM_SECRET>@pay.example.com/connector are sent as a standard Basic Authorization header and aren’t part of the signed URL, so your connector can check both. A long random token in the URL path works too, and it is part of the signed URL.

The connector URL appears in your call logs

The full paymentConnectorUrl, including any credentials or token in it, is visible in your call logs in the Dashboard. Use a secret that protects only this endpoint, and rotate it periodically.