×

You can now send the Payment Message (order_details) and your business can enable customers to pay for their orders using all the UPI Apps installed on their devices via WhatsApp in a user-initiated conversation as a free-form message. Businesses can send customers invoice(order_details) messages, and then get notified about payment status updates via webhook notifications from Payment Gateway, all this is now possible using the Exotel APIs.

Before you start sending the Message with the order details and payment button, you need to complete the setup and follow the steps:

  • Request Access for the Payment APIs for your WhatsApp Business Account - Raise it to Exotel Account Manager
  • Choose and Link the Payment Gateway Account to your WhatsApp Business Account
  • Configure the user journey to purchase the product and initiate the Order Checkout message (You can use our WhatsApp Catalog APIs)
  • Construct the order details message using the API explained below and send the message.

How it Works:

  • Initiating Payment: To kick off the payment process, the business sends an 'order_details' message to the consumer. 
  • Unique Reference ID: Each 'order_details' message carries a unique reference_id provided by the business. 
  • Tracking Order Status: After sending the 'order_details' message, the business patiently awaits payment or transaction status updates directly from the Payment Gateway. 
  • Importance of Payment Signal Update (optional): Updating the user about the payment signal is vital and you can send the payment confirmation message to the users.

Please go through our detailed WhatsApp Payments Integration Guide here.

To send messages to a single number with configured Flow button content via Exotel API, make an HTTP POST request to:

POST

https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/messages

  • Replace <your_api_key> and <your_api_token> with the API key and token created by you.
  • Replace <your_sid> with your “Account sid".
  • Replace <subdomain> with the region of your account
    1. <subdomain> of Singapore cluster is @api.exotel.com
    2. <subdomain> of Mumbai cluster is @api.in.exotel.com

<your_api_key> , <your_api_token> and <your_sid> are available in the API settings page of your Exotel Dashboard

The following are the POST parameters - 

Payment Interactive Object

Parameter Name Parameter Type Mandatory/Optional Parameter Description
type String Optional The type of interactive message you want to send. Supported values:button: Use it for Reply Buttons.
list: Use it for List Messages.
header HeaderObject Optional Header content displayed on top of a message. If a header is not provided, the API uses an image of the first available product as the header
body BodyObject Mandatory Optional for type product. Required for other message types.
footer FooterObject Optional An object with the footer of the message. The object contains the following fields:
text string
action ActionObject Mandatory An action object you want the user to perform after reading the message. This action object contains the following fields:
name string
Must be "review_and_pay"PaymentParametersObject
Refer to PaymentParametersObject for details on the fields

Payment Parameters Object

Parameter Name Parameter Type Mandatory/Optional Parameter Description
reference_id string Mandatory Unique identifier for the order or invoice provided by the business. This cannot be an empty string and can only contain English letters, numbers, underscores, dashes, or dots, and should not exceed 35 characters.
The reference_id must be unique for each order_details message for the same business. If the partner would like to send multiple order_details messages for the same order, invoice, etc. it is recommended to include a sequence number in the reference_id (for example, -) to ensure reference_id uniqueness.
type string Mandatory The type of goods being paid for in this order. supported options are
digital-goods

physical-goods
beneficiaries PaymentBeneficiariesObject Mandatory/Optional Mandatory for shipped physical-goods.
See PayementBeneficiariesObject
payment_type string Mandatory Must be "payment_gateway"
payment_configuration string Mandatory The name of the pre-configured payment configuration to use for this order and must not exceed 60 characters.
currency string Mandatory The currency for this order. Currently the only supported value is INR.
total_amount
object
PaymentAmountObject Mandatory The total_amount object
Refer to PaymentAmountObject for details on the parameters
total_amount.value must be equal to order.subtotal.value + order.tax.value + order.shipping.value - order.discount.value.
object PaymentParametersOrderObject Mandatory See PaymentParametersOrderObject for details on this object

Payment Parameter Items Object

Parameter Name Parameter Type Mandatory/Optional Parameter Description
retailer_id string Mandatory Unique identifier for an item in the order.
name string Mandatory The item’s name to be displayed to the user. Cannot exceed 60 characters
amout string Mandatory The price per item
Refer to PaymentAmount object
sale_amount AmountObject Optional The discounted price per item. This should be less than the original amount. If included, this field is used to calculate the subtotal amount
quantity string Mandatory The number of items in this order, this field cannot be decimal has to be integer.
country_of_origin string Mandatory/Optional Required if catalog_id is not present. Name of the importer company
importer_name string Mandatory/Optional Required if catalog_id is not present. Name of the importer company
importer_address string Mandatory/Optional Required if catalog_id is not present. Name of the importer company

Payment Amount Object

Parameter Name Parameter Type Mandatory/Optional Value
offset Integer Mandatory Must be 100 for INR
value Integer Mandatory Positive integer representing the amount value multiplied by offset. For example, ₹12.34 has value 1234.

Payment Beneficiaries Object (Required for shipped physical-goods)

Parameter Name Parameter Type Mandatory/Optional Parameter Description
name string Mandatory Name of the individual or business receiving the physical goods. Cannot exceed 200 characters
address_line1 string Mandatory Shipping address (Door/Tower Number, Street Name etc.). Cannot exceed 100 characters
address_line2 string Optional Shipping address (Landmark, Area, etc.). Cannot exceed 100 characters
city string Mandatory Name of the city
state string Mandatory Name of the state
country string Mandatory Support values
India
postal_code string Mandatory 6-digit zipcode of shipping address.

Payment Sub Total Amount Object

Parameter Name Parameter Type Mandatory/Optional Parameter Description
offset Integer Mandatory Must be 100 for INR
value Integer Mandatory Positive integer representing the amount value multiplied by offset. For example, ₹12.34 has value 1234.

Payment Tax Amount Object

Parameter Name Parameter Type Mandatory/Optional Parameter Description
offset Integer Mandatory Must be 100 for INR
value Integer Mandatory Positive integer representing the amount value multiplied by offset. For example, ₹12.34 has value 1234.
description string Optional Max character limit is 60 characters

Payment Shipping Cost Object

Parameter Name Parameter Type Mandatory/Optional Parameter Description
offset Integer Mandatory Must be 100 for INR
value Integer Mandatory Positive integer representing the amount value multiplied by offset. For example, ₹12.34 has value 1234.
description string Optional Max character limit is 60 characters

Payment Discount Object

Parameter Name Parameter Type Mandatory/Optional Parameter Description
offset Integer Mandatory Must be 100 for INR
value Integer Mandatory Positive integer representing the amount value multiplied by offset. For example, ₹12.34 has value 1234.
description string Optional Max character limit is 60 characters

Payment Expiration Object

Parameter Name Parameter Type Mandatory/Optional Parameter Description
timestamp string Mandatory UTC timestamp in seconds of time when order should expire. Minimum threshold is 300 seconds
description string Optional Text explanation for expiration. Max character limit is 120 characters
curl --location 'https://{{AuthKey}}:{{AuthToken}}@{{SubDomain}}/v2/accounts/{{AccountSid}}/messages' \
--header 'Content-Type: application/json' \
--data '{
    "whatsapp": {
        "messages": [
            {
                "from": "9199XXXXXXXX",
                "to": "9190XXXXXXXX",
                "content": {
                    "type": "interactive",
                    "interactive": {
                        "type": "order_details",
                        "header": {
                            "type": "image",
                            "image": {
                                "link": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Home_made_sour_dough_bread.jpg/640px-Home_made_sour_dough_bread.jpg"
                            }
                        },
                        "body": {
                            "text": "Click on the Pay Now button to complete the order"
                        },
                        "footer": {
                            "text": "Thank You!"
                        },
                        "action": {
                            "name": "review_and_pay",
                            "parameters": {
                                "reference_id": "57848",
                                "type": "digital-goods",
                                "payment_type": "payment_gateway:razorpay",
                                "payment_configuration": "XXXXXXXX",
                                "currency": "INR",
                                "total_amount": {
                                    "value": 1000,
                                    "offset": 100
                                },
                                "order": {
                                    "status": "pending",
                                    "items": [
                                        {
                                            "retailer_id": "1234567",
                                            "name": "Bread",
                                            "amount": {
                                                "value": 1400,
                                                "offset": 100
                                            },
                                            "sale_amount": {
                                                "value": 1000,
                                                "offset": 100
                                            },
                                            "quantity": 1
                                        }
                                    ],
                                    "subtotal": {
                                        "value": 1000,
                                        "offset": 100
                                    },
                                    "tax": {
                                        "value": 100,
                                        "offset": 100,
                                        "description": "GST Inclusive"
                                    },
                                    "shipping": {
                                        "value": 0,
                                        "offset": 100,
                                        "description": "Via Postal"
                                    },
                                    "discount": {
                                        "value": 100,
                                        "offset": 100,
                                        "description": "For Premium Customers",
                                        "discount_program_name": "optional_text"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        ]
    }
}'
import requests
import json

url = "https://{{AuthKey}}:{{AuthToken}}@{{SubDomain}}/v2/accounts/{{AccountSid}}/messages"

payload = json.dumps({
  "whatsapp": {
    "messages": [
      {
        "from": "9172XXXXXXXX",
        "to": "9190XXXXXXXX",
        "content": {
          "type": "interactive",
          "interactive": {
            "type": "order_details",
            "header": {
              "type": "image",
              "image": {
                "link": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Home_made_sour_dough_bread.jpg/640px-Home_made_sour_dough_bread.jpg"
              }
            },
            "body": {
              "text": "Click on the Pay Now button to complete the order"
            },
            "footer": {
              "text": "Thank You!"
            },
            "action": {
              "name": "review_and_pay",
              "parameters": {
                "reference_id": "57848",
                "type": "digital-goods",
                "payment_type": "payment_gateway:razorpay",
                "payment_configuration": "Checking-paymentstest",
                "currency": "INR",
                "total_amount": {
                  "value": 1000,
                  "offset": 100
                },
                "order": {
                  "status": "pending",
                  "items": [
                    {
                      "retailer_id": "1234567",
                      "name": "Bread",
                      "amount": {
                        "value": 1400,
                        "offset": 100
                      },
                      "sale_amount": {
                        "value": 1000,
                        "offset": 100
                      },
                      "quantity": 1
                    }
                  ],
                  "subtotal": {
                    "value": 1000,
                    "offset": 100
                  },
                  "tax": {
                    "value": 100,
                    "offset": 100,
                    "description": "GST Inclusive"
                  },
                  "shipping": {
                    "value": 0,
                    "offset": 100,
                    "description": "Via Postal"
                  },
                  "discount": {
                    "value": 100,
                    "offset": 100,
                    "description": "For Premium Customers",
                    "discount_program_name": "optional_text"
                  }
                }
              }
            }
          }
        }
      }
    ]
  }
})
headers = {
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

curl --location 'https://{{AuthKey}}:{{AuthToken}}@{{SubDomain}}/v2/accounts/{{AccountSid}}/messages' \
--header 'Content-Type: application/json' \
--data '{
    "status_callback": "https://webhook.site/90XXXXXX",
    "whatsapp": {
        "messages": [
            {
                "from": "+9180XXXXXXXX",
                "to": "+9186XXXXXXXX",
                "content": {
                    "type": "interactive",
                    "interactive": {
                        "type": "order_details",
                        "header": {
                            "type": "image",
                            "image": {
                                "link": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Home_made_sour_dough_bread.jpg/640px-Home_made_sour_dough_bread.jpg"
                            }
                        },
                        "body": {
                            "text": "Click on the Pay Now button to complete the order"
                        },
                        "footer": {
                            "text": "Thank You!"
                        },
                        "action": {
                            "name": "review_and_pay",
                            "parameters": {
                                "type": "digital-goods",
                                "currency": "INR",
                                "reference_id": "1033XXXX",
                                "payment_type": "upi",
                                "payment_configuration": "XXXXXXXX",
                                "total_amount": {
                                    "value": 1100,
                                    "offset": 100
                                },
                                "order": {
                                    "status": "pending",
                                    "items": [
                                        {
                                            "retailer_id": "12345678",
                                            "name": "Bread",
                                            "amount": {
                                                "value": 1500,
                                                "offset": 100
                                            },
                                            "sale_amount": {
                                                "value": 1000,
                                                "offset": 100
                                            },
                                            "quantity": 1
                                        }
                                    ],
                                    "subtotal": {
                                        "value": 1000,
                                        "offset": 100
                                    },
                                    "tax": {
                                        "value": 100,
                                        "offset": 100,
                                        "description": "GST Inclusive"
                                    },
                                    "shipping": {
                                        "value": 100,
                                        "offset": 100,
                                        "description": "Via Postal"
                                    },
                                    "discount": {
                                        "value": 100,
                                        "offset": 100,
                                        "description": "For Premium Customers",
                                        "discount_program_name": "optional_text"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        ]
    }
}'

HTTP Response:

  • On success, the HTTP response status code will be 202
  • The Sid is the unique identifier of the message and it will be useful to log this for future debugging
  • the HTTP body will contain a JSON similar to the one below
{
   "request_id": "b434e92XXXXXXXXXXXXX",
   "method": "POST",
   "http_code": 202,
   "metadata": {
     "failed": 0,
     "total": 1,
     "success": 1
   },
   "response": {
     "whatsapp": {
       "messages": [
         {
           "code": 202,
           "error_data": null,
           "status": "success",
           "data": {
             "sid": "2FdiXXXXXXXXXXXXXXXXX",
           }
         }
       ]
     }
   }
}

The following are the response parameters - 

Parameter Type Mandatory/Optional Notes
request_id String Mandatory This indicates the unique id of the request. Useful for debugging and tracing purposes.
method String Mandatory This indicates the HTTP method for the request such as POST
http_code Integer Mandatory This indicates the HTTP code for the request such as 202, 400, 500 etc.
metadata Metadata Object Mandatory Metadata pertaining to the request. Count of failed, total and success records.
response Response Object Mandatory Response for the request

Metadata Object

Parameter Type Mandatory/Optional Notes
total Integer Mandatory Total number of the messages in the request
success Integer Mandatory Number of messages successfully accepted
failed Integer Mandatory Number of messages that couldn’t be accepted (failed)

Response Object

Parameter Type Mandatory/Optional Notes
whatsapp Channel Response Object Mandatory Response for Whatsapp messages specified in the request

Channel Response Object

Parameter Type Mandatory/Optional Notes
messages []Create Message Response Object Mandatory Array of messages response for each message

Create Message Response Object

Parameter Type Mandatory/Optional Notes
code Integer Mandatory Response code for the individual message
error_data Error Response Object Optional Error related to a single message
status String Mandatory Status of the single message
data Message Response Object Optional Data pertaining to a single message

Error Response Object

Parameter Type Mandatory/Optional Notes
code Numeric Mandatory Numeric HTTP code for a Single message
message String  Mandatory Brief description of the error
description String Mandatory Detailed explanation of error

Message Response Object

Parameter Type Mandatory/Optional Notes
sid String Mandatory SID (Unique identifier) of the single message

HTTP Error codes : 

HTTP Error Codes Error Message
202 Accepted - Request accepted.
400 Bad Request - Something in your header or request body was malformed/missing.More than 100 messages specified in a request
401 Unauthorized - Necessary credentials were either missing or invalid.
402 Payment Required - The action is not available on your plan, or you have exceeded usage limits for your current plan.
403 Your credentials are valid, but you don’t have access to the requested resource.
404 Not Found - The object you’re requesting doesn’t exist.
5xx Server Errors - Something went wrong at our end. Please try again.

Status Callbacks 

Status callback URL can be passed in the status_callback parameter in send message APIs and it can also be configured as default to receive the responses. The Exotel team will help you configure the default URL while onboarding.

  • If you pass the status callback URL in send message/template message APIs, status callbacks will be triggered to the respective URL 
  • If you do not pass any status callback URL in send message/template message APIs, status callbacks will be triggered to the default URL, if any.

*NOTE: Any callback can be received only in one status callback URL at any time.

The sent message with the Order Details message will have a different Status Callback when the user completes the payment.

 
{
  "params": {
    "whatsapp": {
      "messages": [
        {
          "callback_type": "dlr",
          "sid": "e96ec08a641612cXXXXXXXX",
          "to": "+9186XXXXXXXX",
          "exo_status_code": 30002,
          "exo_detailed_status": "EX_MESSAGE_DELIVERED",
          "description": "Message Delivered",
          "timestamp": "2024-05-21T12:48:26+05:30",
          "payment_reference_id": "1033XX",
          "conversation_id": "675625c2XXXXXXX"
        }
      ]
    }
  }
{
  "params": {
    "whatsapp": {
      "messages": [
        {
          "callback_type": "dlr",
          "sid": "e96ec08a641612cXXXXXXXX",
          "to": "+9186XXXXXXXX",
          "exo_status_code": 30050,
          "exo_detailed_status": "EX_PAYMENT_SUCCESS",
          "description": "Payment Succeeded",
          "timestamp": "2024-05-21T12:53:54+05:30",
          "payment_reference_id": "1033XX",
          "payment_transaction_id": "41428XXXXX"
        }
      ]
    }
  }
}

Update Order Status

You can now send the Payment Order Status message to the end-users after the payment success or failure to update the status of the order purchase using our Exotel APIs.

POST

https://<your_api_key>:<your_api_token><subdomain>/v2/accounts/<your_sid>/messages

Upon receiving transaction signals from the payment gateway through webhook, the business must update the order status to keep the user up to date. Currently, we support the following order status values:

Value Description
pending User has not successfully paid yet
processing User payment authorized, merchant/partner is fulfilling the order, performing service, etc.
partially-shipped A portion of the products in the order have been shipped by the merchant
shipped All the products in the order have been shipped by the merchant
completed The order is completed and no further action is expected from the user or the partner/merchant
canceled The partner/merchant would like to cancel the order_details message for the order/invoice. The status update will fail if there is already a successful or pending payment for this order_details message

Typically businesses update the order_status using either the WhatsApp payment status change notifications or their own internal processes. To update order_status, the partner sends an order_status message to the user.

curl --location --globoff 'https://{{AuthKey}}:{{AuthToken}}@{{SubDomain}}/v2/accounts/{{AccountSid}}/messages' \
--header 'Content-Type: application/json' \
--data '{
    "custom_data": "ORDXXXXXX",
    "status_callback": "https://webhook.site",
    "whatsapp": {
        "messages": [
            {
                "from": "{{FromNumber}}",
                "to": "{{ToNumber}}",
                "content": {
                    "type": "interactive",
                    "interactive": {
                        "type": "order_status",
                        "body": {
                            "text": "your-text-body-content"
                        },
                        "action": {
                            "name": "review_order",
                            "parameters": {
                                "reference_id": "reference-id-value",
                                "order": {
                                    "status":"processing | partially_shipped | shipped | completed | canceled",
                                    "description": "optional-text"
                                }
                            }
                        }
                    }
                }
            }
        ]
    }
}'
import requests
import json

url = "https://{{AuthKey}}:{{AuthToken}}@{{SubDomain}}/v2/accounts/{{AccountSid}}/messages"

payload = json.dumps({
"custom_data": "ORDER12356894",
"status_callback": "https://eoj72flq0lwx2gs.m.pipedream.net/",
"whatsapp": {
"messages": [
{
"from": "{{FromNumber}}",
"to": "{{ToNumber}}",
"content": {
"type": "interactive",
"interactive": {
"type": "order_status",
"body": {
"text": "your-text-body-content"
},
"action": {
"name": "review_order",
"parameters": {
"reference_id": "reference-id-value",
"order": {
"status": "processing | partially_shipped | shipped | completed | canceled",
"description": "optional-text"
}
}
}
}
}
}
]
}
})
headers = {
'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)