Create an order
const url = 'https://cybqa.pesapal.com/pesapalv3/api/Transactions/SubmitOrderRequest';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"id":"example","currency":"KES","amount":1,"description":"example","callback_url":"example","cancellation_url":"example","notification_id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","billing_address":{"email_address":"example","phone_number":"example","first_name":"example","middle_name":"example","last_name":"example","country_code":"example","line_1":"example","line_2":"example","city":"example","state":"example","postal_code":"example","zip_code":"example"},"branch":"example","redirect_mode":"TOP_WINDOW","account_number":"example","subscription_details":{"start_date":"01-01-2027","end_date":"01-01-2028","frequency":"DAILY"}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://cybqa.pesapal.com/pesapalv3/api/Transactions/SubmitOrderRequest \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "id": "example", "currency": "KES", "amount": 1, "description": "example", "callback_url": "example", "cancellation_url": "example", "notification_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "billing_address": { "email_address": "example", "phone_number": "example", "first_name": "example", "middle_name": "example", "last_name": "example", "country_code": "example", "line_1": "example", "line_2": "example", "city": "example", "state": "example", "postal_code": "example", "zip_code": "example" }, "branch": "example", "redirect_mode": "TOP_WINDOW", "account_number": "example", "subscription_details": { "start_date": "01-01-2027", "end_date": "01-01-2028", "frequency": "DAILY" } }'Creates an order and returns a redirect_url to send the customer to.
Adding account_number and subscription_details turns the same request
into a recurring payment. There is no separate subscriptions endpoint.
Submitting an id that has been used before returns the original
order, at its original amount, with no error. Generate a fresh id for
every attempt.
Unknown fields are silently ignored rather than rejected, so a misspelled field name produces no error and simply has no effect.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
Your own reference, which must be unique. Values between 51 and 150 characters fail with a generic decline that does not mention length.
Validated against a lookup table. Acceptance at submission does not guarantee a customer can complete a payment in that currency.
Shown to the customer. No maximum length was enforced.
Plain HTTP is accepted.
An ipn_id from registerIpn.
The object is mandatory on an order, but every field within it is optional. An empty object is accepted.
object
ISO 3166-1 alpha-2, for example KE.
Your identifier for a subscriber. Send with subscription_details.
object
Format dd-MM-yyyy. ISO 8601 is rejected. Must be in the future.
Format dd-MM-yyyy. The range is not validated, so an end date
before the start date is accepted.
Case insensitive.
Responses
Section titled “Responses”Returned for both success and failure.
object
Pesapal’s identifier for the order. Required by most other operations.
The id that was sent.
Send the customer here to pay.
An application error, returned with HTTP 200.
object
A populated error. Every field is present and non-null.
object
Mostly snake_case, but not consistently: InvalidIpnId is
PascalCase. Compare exactly.
Multiple validation errors arrive pipe-delimited in one string, and malformed JSON produces leading empty segments.
Usually "500". Never matches the HTTP status.
Example
{ "error": { "error_type": "api_error" }}