Notch Pay
Notch Pay is a payment gateway enabling businesses to accept and send money via mobile money (MTN MoMo, Orange Money, M-Pesa, Wave, Airtel) across Cameroon, Côte d'Ivoire, Senegal, Kenya, Uganda and Ghana. Each market settles in its local currency (XAF in CM; XOF in CI/SN; KES, UGX, GHS).
Use with AI agents
After installing the plugin or adding the MCP server, prompt your agent:
Capabilities
| Capability | Type | StatusiVerified — tested in a real application.Available — spec complete, usable by agents. | Method | Example |
|---|---|---|---|---|
| block_customer | synchronous | Available | POST | Example |
| cancel_payment | synchronous | Available | DELETE | Example |
| create_beneficiary | synchronous | Available | POST | Example |
| create_customer | synchronous | Available | POST | Example |
| create_payment | synchronous | Available | POST | Example |
| create_webhook | synchronous | Available | POST | Example |
| delete_beneficiary | synchronous | Available | DELETE | Example |
| delete_customer | synchronous | Available | DELETE | Example |
| delete_webhook | synchronous | Available | DELETE | Example |
| get_balance | synchronous | Available | GET | Example |
| get_beneficiary | synchronous | Available | GET | Example |
| get_customer | synchronous | Available | GET | Example |
| get_webhook | synchronous | Available | GET | Example |
| list_beneficiaries | synchronous | Available | GET | Example |
| list_channels | synchronous | Available | GET | Example |
| list_countries | synchronous | Available | GET | Example |
| list_currencies | synchronous | Available | GET | Example |
| list_customer_payment_methods | synchronous | Available | GET | Example |
| list_customer_payments | synchronous | Available | GET | Example |
| list_customers | synchronous | Available | GET | Example |
| list_payments | synchronous | Available | GET | Example |
| list_transfers | synchronous | Available | GET | Example |
| list_webhooks | synchronous | Available | GET | Example |
| process_payment | synchronous | Available | POST | Example |
| send_transfer | synchronous | Available | POST | Example |
| unblock_customer | synchronous | Available | POST | Example |
| update_customer | synchronous | Available | POST | Example |
| update_webhook | synchronous | Available | PUT | Example |
| verify_payment | synchronous | Available | GET | Example |
| verify_transfer | synchronous | Available | GET | Example |
| webhook_payment_completed | webhook | Available | POST | Example |
Gotchas
block_customer
- ⚠Blocking a customer is reversible — use unblock_customer to restore access. However, blocking does not cancel any payments already in progress at the time of the block.
- ⚠The request body must be empty or omitted entirely. Sending a non-empty JSON body may cause unexpected behavior.
- ⚠Calling block_customer on an already-blocked customer may return 200 without error. Do not rely on the response to determine whether the customer was previously unblocked.
cancel_payment
- ⚠Only payments with status 'pending' can be canceled. Payments that are 'processing', 'complete', or 'failed' cannot be canceled and return a 422 error.
- ⚠Cancellation is irreversible. Once canceled, the reference cannot be reused to restart the payment — create a new payment instead.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
create_beneficiary
- ⚠The Authorization header value is your public key with no prefix — do not add 'Bearer ' or 'Basic ' before it.
- ⚠The channel slug must exactly match a Notch Pay supported channel (e.g. "cm.mtn", "cm.orange"). An invalid channel returns a 422 error without listing valid values.
- ⚠account_number must be a valid phone number for mobile money channels — include the country code (e.g. +237XXXXXXXXX). Passing just a local number may fail silently or create an unusable beneficiary.
- ⚠Store the returned beneficiary.id immediately — it is required to initiate transfers to this recipient and to delete the beneficiary later.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
- ⚠The channel slug determines the settlement currency: cm.* → XAF, ci.*/sn.* → XOF, ke.* → KES, ug.* → UGX, gh.* → GHS. The currency does not need to be specified at beneficiary creation but is fixed by the channel.
create_customer
- ⚠At least one of email or phone is required. Submitting only name returns a 422 validation error — the Notch Pay docs make 'name' look mandatory but the API really enforces 'one of email/phone'.
- ⚠The Authorization header must contain the public key directly as the token value — not prefixed with 'Bearer'. The format is exactly the key string.
- ⚠Customer IDs returned by Notch Pay are opaque strings. Do not apply encodeURIComponent to these IDs when constructing path-based URLs — pass them as-is.
- ⚠The address object uses 'address_line1' / 'address_line2' (and optional 'state'), not 'street'. Sending 'street' silently drops the field.
create_payment
- ⚠Always verify payment status server-side by calling verify_payment (GET /payments/{reference}) before fulfilling an order. The callback redirect can be forged by the user.
- ⚠The transaction object has no 'id' field — use 'reference' (format: trx.xxx) as the unique identifier. The 'sandbox' field is an integer (1 = sandbox, 0 = live), not a boolean.
- ⚠Use a unique, idempotent reference per payment. Reusing a reference across different transactions will cause reconciliation errors.
- ⚠The Authorization header value is your public key with no prefix — do not add 'Bearer ' before it.
- ⚠For sandbox testing, use a public key containing 'test_' in its value. The same API endpoint is used for both test and live keys.
- ⚠At least one of email, phone, or customer is required. Omitting all three returns a 422 error.
- ⚠The currency must match the channel's country: XAF for cm.* channels (Cameroon), XOF for ci.*/sn.* (Côte d'Ivoire/Senegal), KES for ke.* (Kenya), UGX for ug.* (Uganda), GHS for gh.* (Ghana). A mismatch between currency and channel returns 422.
create_webhook
- ⚠The webhook URL must be HTTPS. HTTP URLs are rejected with a 422 error.
- ⚠Store the endpoint.id from the response — it is required to call update_webhook or delete_webhook later.
- ⚠The events array must not be empty. Subscribing to no events is rejected. Only subscribe to event types your server actually handles.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠This API registers the webhook configuration on the Notch Pay side. To validate incoming payloads, you must verify the X-Notch-Signature header on your receiving endpoint.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
delete_beneficiary
- ⚠The Authorization header value is your public key with no prefix — do not add 'Bearer ' before it.
- ⚠Deletion is permanent and irreversible. There is no archive or soft-delete — once deleted, the beneficiary ID cannot be recovered.
- ⚠A 404 response means either the beneficiary does not exist or it belongs to a different API key. Beneficiaries are scoped to the account that created them.
- ⚠Do not URL-encode the beneficiary ID when inserting it into the path — pass it as-is.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
delete_customer
- ⚠Deletion is permanent and irreversible. There is no soft-delete or recycle bin — once deleted, the customer and all associated data cannot be recovered via the API.
- ⚠A 404 is returned if the customer ID does not exist or no longer belongs to your account. Check for 404 separately from other error codes.
- ⚠Deleting a customer does not automatically cancel or refund any associated payments. Resolve outstanding transactions before deleting the customer record.
delete_webhook
- ⚠Deletion is immediate and irreversible. Notch Pay will stop delivering events to the endpoint right away.
- ⚠The endpoint path parameter is the webhook endpoint ID, not its URL.
- ⚠A 404 is returned if the endpoint ID does not exist or belongs to a different account.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
get_balance
- ⚠Use balance.available (not balance.total) to determine whether you have enough funds to initiate a transfer. total includes reserved and pending amounts that are not yet usable.
- ⚠The response includes an environment field — verify it matches your expected mode ("live" vs "sandbox") to avoid acting on test data in production.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
get_beneficiary
- ⚠The Authorization header value is your public key with no prefix — do not add 'Bearer ' before it.
- ⚠The id path parameter must be the Notch Pay beneficiary ID (from create_beneficiary response), not the account_number or phone number.
- ⚠A 404 response means the beneficiary does not exist under the authenticated account — beneficiaries are scoped to the API key used to create them.
- ⚠Do not URL-encode the beneficiary ID when inserting it into the path — pass it as-is.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
get_customer
- ⚠A 404 is returned when the customer ID does not exist or does not belong to your account. Always handle 404 explicitly rather than treating it as a generic error.
- ⚠Customer IDs are opaque strings — pass them as-is into the URL path. Do not apply any encoding transformation to the ID.
- ⚠The customer object does not include a 'blocked' or 'address' field — these are not returned by the API. Use block_customer / unblock_customer endpoints to manage block status.
get_webhook
- ⚠The endpoint path parameter is the webhook endpoint ID, not its URL. Use the id field from create_webhook or list_webhooks.
- ⚠A 404 is returned if the endpoint ID does not exist or belongs to a different account.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
list_beneficiaries
- ⚠The Authorization header value is your public key with no prefix — do not add 'Bearer ' before it.
- ⚠Query parameters (limit, page, search) must be appended to the URL as a query string — they are not sent in the request body.
- ⚠When current_page equals last_page, you have reached the end of the list. Requesting beyond last_page returns an empty items array rather than an error.
- ⚠Pagination uses 'totals' (not 'total') and 'selected' (not 'per_page'). Reading the wrong field name silently returns undefined.
- ⚠The totals field reflects the count at the time of the request; concurrent creates or deletes can cause inconsistencies when paginating large lists.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
list_channels
- ⚠Always filter by country and currency before displaying channels to users — not all channels are available in every country, and showing irrelevant options degrades UX.
- ⚠Use min_amount and max_amount to validate the user's payment amount before initiating a transaction; submitting an out-of-range amount will result in a rejected payment.
- ⚠Store the channel slug (not the id or name) when initiating a payment — it is the required identifier in subsequent API calls.
- ⚠Cache the response — channel data changes rarely, but min/max amounts can be updated by Notch Pay without notice. A daily refresh is sufficient.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
list_countries
- ⚠This endpoint returns ALL 249 ISO 3166-1 countries — it is a reference/validation list, NOT a list of countries where Notch Pay operates. To find where Notch Pay actually has active channels, call GET /channels filtered by country instead.
- ⚠Use the country code (not the country name) when filtering channels or initiating payments; the name is for display purposes only.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
list_currencies
- ⚠This endpoint returns ALL world currencies (ISO 4217 global list), NOT only currencies accepted by Notch Pay — it is a reference/validation endpoint. The actual currency used across all Notch Pay channels is XAF regardless of the customer's country.
- ⚠Use the 'faction' field to format amounts correctly — XAF has faction: 0, so amounts must be integers. Sending 1000.50 for XAF will cause a validation error.
- ⚠Use the currency code (not the name) when specifying currency in payment or channel API calls; the name field is for display purposes only.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
list_customer_payment_methods
- ⚠A 404 is returned if the customer ID does not exist. The customer ID must come from create_customer or list_customers — do not use a payment reference or beneficiary ID.
- ⚠The items array may be empty if the customer has no saved payment methods — this is not an error. A customer only accumulates payment methods after completing at least one process_payment call.
- ⚠The response schema for this endpoint was not live-tested (May 2026) — field names and structure are based on the OpenAPI spec and may differ from actual responses. Treat as draft.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
list_customer_payments
- ⚠A 404 is returned if the customer ID does not exist. Verify the customer exists via get_customer before listing their payments.
- ⚠Pagination query parameters (limit, page) are passed as URL query string parameters. The customer ID is in the URL path, not the query string.
- ⚠The payments array may be empty if the customer has no payments yet — this is not an error condition. Always check payments.length before iterating.
- ⚠The top-level array is named 'payments' (not 'items') and pagination uses 'totals' (not 'total') and 'selected' (not 'per_page'). Reading the wrong field name silently returns undefined.
list_customers
- ⚠Query parameters (limit, page, search) are passed as URL query string parameters, not in the request body. A GET request body is ignored.
- ⚠Pagination is 1-based: the first page is page=1. Passing page=0 may return an empty result set or an error.
- ⚠The 'totals' field (not 'total') returns the count of all matching customers. The 'selected' field (not 'per_page') indicates how many items were returned in this page. Using 'total' or 'per_page' will return undefined.
- ⚠Pagination uses 'totals' (not 'total') and 'selected' (not 'per_page').
list_payments
- ⚠All query parameters are optional — omitting all of them returns the full paginated list with the API default page size.
- ⚠date_start and date_end must be ISO 8601 strings. Passing plain dates without a time component (e.g. "2024-01-01") may be rejected or interpreted differently by the API.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠Iterate through pages using the page param and stop when current_page equals last_page to avoid requesting beyond the last page.
- ⚠Pagination uses 'totals' (not 'total') and 'selected' (not 'per_page') — using the wrong field name will silently return undefined.
list_transfers
- ⚠The Authorization header value is your public key with no prefix — do not add 'Bearer ' before it.
- ⚠All transfer endpoints require an additional `X-Grant` header for enhanced security. Pass the grant token generated in your Notch Pay dashboard as `X-Grant: {token}`. Requests without this header return 401.
- ⚠Pagination starts at page 1, not page 0. Passing page=0 may return an unexpected result or an error.
- ⚠date_start and date_end must be ISO 8601 formatted strings. Passing plain date strings (e.g. '2024-01-01') without time and timezone may be rejected or silently ignored.
- ⚠A transfer with status 'pending' is still in progress — do not treat it as completed. Only 'complete' status confirms a successful payout.
- ⚠Pagination uses 'totals' (not 'total') and 'selected' (not 'per_page'). Reading the wrong field name silently returns undefined.
- ⚠The totals field reflects the count across all pages, not just the current page. Use last_page to know when you have fetched all records.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
list_webhooks
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠All query parameters are optional. Omitting them returns the full paginated list with the API default page size.
- ⚠The top-level array is named 'endpoints' (not 'items'). Pagination uses 'totals' (not 'total') and 'selected' (not 'per_page'). Reading the wrong field name silently returns undefined.
- ⚠Iterate through pages by incrementing the page param and stop when current_page equals last_page.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
process_payment
- ⚠process_payment must be called after create_payment — it requires the reference from the created transaction and will return 404 if the reference does not exist.
- ⚠A 200 response with status "Accepted" means the payment was submitted to the mobile operator, not that it has been confirmed. Always use verify_payment afterwards to check the final status before fulfilling an order.
- ⚠The phone number in data.phone must be in E.164 format (e.g. "+237670000000"). Passing a local format without the country code will cause the mobile operator to reject the request.
- ⚠The channel slug is country-and-operator specific (e.g. "cm.mtn", "cm.orange"). Using a slug that does not match the customer's operator will result in a failed payment.
- ⚠Calling process_payment on a transaction that is already 'complete', 'canceled', or 'failed' returns 422 with message 'Payment cannot be processed'.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠The API also accepts PUT /payments/{reference} with the same body and response — this is a legacy alias. Prefer POST.
send_transfer
- ⚠Transfers are not immediately settled — the initial status is "pending". Always call verify_transfer (GET /transfers/{reference}) to confirm completion before recording the disbursement as successful.
- ⚠Either beneficiary (saved beneficiary ID) or the recipient+channel pair is required. Providing neither returns a 422 error.
- ⚠All transfer endpoints require an additional `X-Grant` header for enhanced security. Generate this grant token in your Notch Pay dashboard and pass it as `X-Grant: {token}`. Requests without this header return 401.
- ⚠Your server's IP address must be whitelisted in the Notch Pay dashboard before API transfers will be accepted. Requests from non-whitelisted IPs are rejected regardless of valid API credentials.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
- ⚠The currency must match the channel's country: XAF for cm.* channels (Cameroon), XOF for ci.*/sn.* (Côte d'Ivoire/Senegal), KES for ke.* (Kenya), UGX for ug.* (Uganda), GHS for gh.* (Ghana). A mismatch returns 422.
unblock_customer
- ⚠Calling unblock_customer on a customer that is not currently blocked may return 200 without error. Use get_customer to check the blocked field before unblocking if idempotency matters.
- ⚠The request body must be empty or omitted entirely. Sending a non-empty JSON body may cause unexpected behavior.
- ⚠After unblocking, the customer can immediately initiate new payments. Ensure any fraud review or compliance checks are completed before calling unblock_customer.
update_customer
- ⚠Notch Pay uses POST (not PATCH or PUT) for partial updates. Only the fields included in the body are updated; omitted fields retain their current values.
- ⚠Sending an empty body or only the id field will return a 422 validation error — include at least one field to update in the request body.
- ⚠Customer IDs are opaque strings — pass them as-is into the URL path without any encoding.
- ⚠The address object uses 'address_line1' / 'address_line2' (and optional 'state'), not 'street'. Sending 'street' silently drops the field.
update_webhook
- ⚠Updating the events field replaces the entire list — it is not a merge. To add a single event type, fetch the current list via get_webhook first, append the new event, then send the full updated array.
- ⚠The endpoint path parameter is the webhook endpoint ID, not its URL.
- ⚠At least one body field (url, events, or description) must be provided; sending an empty body may return a 422.
- ⚠The new URL must be HTTPS. HTTP URLs are rejected.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
verify_payment
- ⚠Only fulfill an order when transaction.status is "complete" — "pending" means the user has not paid yet.
- ⚠The reference must come from the transaction object returned by create_payment. Do not use values from the callback URL parameters, which can be tampered with.
- ⚠Use your public key in the Authorization header with no 'Bearer ' prefix — same key as create_payment.
verify_transfer
- ⚠Only record a disbursement as successful when transfer.status is "complete". "pending" means the mobile money network has not yet confirmed delivery.
- ⚠All transfer endpoints require an additional `X-Grant` header for enhanced security. Pass the grant token generated in your Notch Pay dashboard as `X-Grant: {token}`. Requests without this header return 401.
- ⚠The Authorization header uses your public key with no 'Bearer ' prefix.
- ⚠This endpoint requires your Notch Pay **private key** in the Authorization header, not the public key used for create_payment. Using a public key returns 406 Not Acceptable.
webhook_payment_completed
- ⚠Never fulfill an order based solely on receiving a webhook — always call verify_payment (GET /payments/{reference}) server-side to confirm the transaction status before acting.
- ⚠Verify the signature using `crypto.timingSafeEqual` against the raw request body BEFORE parsing JSON. A simple string comparison (`===`) is vulnerable to timing attacks.
- ⚠Webhook handlers must be idempotent — Notch Pay retries on non-2xx responses with increasing delays, which can cause duplicate event delivery. Store processed event IDs in your database and skip already-processed events.
- ⚠The `payment.processing` status means the payment is in progress on the mobile money network — do not fulfill the order yet. Only act on `payment.complete`.
- ⚠Register your webhook URL in the Notch Pay dashboard and select the events to subscribe to (e.g. payment.complete). The endpoint URL must be HTTPS.
Details
- Category
- payment
- Sandbox
- Yes