Register an IPN URL
const url = 'https://cybqa.pesapal.com/pesapalv3/api/URLSetup/RegisterIPN';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"url":"example","ipn_notification_type":"GET"}'};
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/URLSetup/RegisterIPN \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "url": "example", "ipn_notification_type": "GET" }'Registers a URL Pesapal calls when an order changes, returning an
ipn_id to pass as notification_id when creating orders.
Registration is idempotent by URL: re-registering an existing URL returns
the original ipn_id and its original created_date rather than
creating a duplicate.
The URL is checked for shape only. It is not resolved, not required to use HTTPS, and never contacted, so a mistyped URL registers successfully and then silently never delivers.
This endpoint is rate limited to roughly one request every 8 seconds,
measured from the last success. Exceeding it returns HTTP 409 with no
Retry-After header.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
Checked for shape only. Never contacted.
Case insensitive. Validated by length before value, so a value longer than 4 characters returns a message about length rather than about the accepted values.
Responses
Section titled “Responses”Returned for both success and failure.
object
UTC. A newly created registration returns seven fractional-second
digits and a Z. Reading an existing registration back returns the
same instant with trailing zeros trimmed and no Z, so the format is
not fixed.
Pass as notification_id when creating an order.
0 is GET, 1 is POST.
1 is Active. No other value has been observed.
Misspelled in the API. Spelling it correctly yields no value.
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
{ "created_date": "2026-09-04T12:46:57.2509054Z", "notification_type": 0, "ipn_notification_type_description": "GET"}No Authorization header was sent.
Returned by HTTP 401 and 409. The error object is JSON-encoded into a
string, so message must be parsed a second time to reach the code.
object
Example
{ "message": "{\"error\":{\"error_type\":\"authentication_error\",\"code\":\"invalid_api_credentials_provided\",\"message\":\"Invalid or Missing Credentials Provided\"},\"status\":\"401\"}"}Rate limited. Roughly one request per 8 seconds is allowed.
Returned by HTTP 401 and 409. The error object is JSON-encoded into a
string, so message must be parsed a second time to reach the code.
object
Example
{ "message": "{\"error\":{\"error_type\":\"authentication_error\",\"code\":\"invalid_api_credentials_provided\",\"message\":\"Invalid or Missing Credentials Provided\"},\"status\":\"401\"}"}