MENU navbar-image

PaySuite API Documentation

PaySuite enables secure payment processing through MPesa, Emola, and Credit/Debit Card Payments, with real-time transaction updates and detailed reports.

Payment Methods

Available payment methods for your customers:

  1. MPesa

    • Mobile money payments
    • Instant processing
    • Requires valid MPesa number
  2. Emola

    • Digital wallet payments
    • Instant processing
    • Requires valid Emola account
  3. Credit/Debit Card

    • Processing time: 1-2 business days
    • Requires valid card details

Webhooks

Get instant notifications for payment events.

Setup

  1. Add your webhook URL in merchant settings
  2. Save your webhook secret securely
  3. Use the secret to validate webhooks

Events

payment.success

Sent when payment succeeds.

{
  "event": "payment.success",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "amount": 100.50,
    "reference": "INV2024001",
    "transaction": {
      "id": "tr_123456",
      "method": "mpesa",
      "paid_at": "2024-02-10T10:15:00.000000Z"
    }
  },
  "created_at": 1708235285,
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}

payment.failed

Sent when payment fails.

{
  "event": "payment.failed",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "amount": 100.50,
    "reference": "INV2024001",
    "error": "Insufficient funds"
  },
  "created_at": 1708235285,
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}

Security

Verify webhook authenticity:

<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];
$secret = 'your_webhook_secret';

$calculatedSignature = hash_hmac('sha256', $payload, $secret);

if (hash_equals($signature, $calculatedSignature)) {
    $data = json_decode($payload, true);
    // Process the event
}

Headers

Header Description
X-Webhook-Signature Request signature
X-Account-Id Your merchant account ID
Content-Type application/json
User-Agent PaySuite-Webhook/1.0

Best Practices

  1. Always verify signatures
  2. Process events once (check request_id)
  3. Respond within 5 seconds
  4. Implement retry handling
  5. Log events for debugging
  6. Test in sandbox before going live

Authenticating requests

Include your API token in all requests:

curl -X POST "https://paysuite.tech/api/v1/payments" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"

Get your API token from the merchant dashboard under Settings > API Access.

Error Handling

All API responses use this format:

{
  "status": "error",
  "message": "Invalid input"
}

Common error codes:

Rate limits:

Payment Requests

APIs for managing payment requests

Create Payment Request

requires authentication

Creates a new payment request in the system.

Example request:
curl --request POST \
    "https://paysuite.tech/api/v1/payments" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"amount\": \"100.50\",
    \"reference\": \"INV2024001\",
    \"description\": \"Payment for invoice INV2024001\",
    \"return_url\": \"https:\\/\\/example.com\\/success\"
}"
const url = new URL(
    "https://paysuite.tech/api/v1/payments"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "amount": "100.50",
    "reference": "INV2024001",
    "description": "Payment for invoice INV2024001",
    "return_url": "https:\/\/example.com\/success"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://paysuite.tech/api/v1/payments';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'amount' => '100.50',
            'reference' => 'INV2024001',
            'description' => 'Payment for invoice INV2024001',
            'return_url' => 'https://example.com/success',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://paysuite.tech/api/v1/payments'
payload = {
    "amount": "100.50",
    "reference": "INV2024001",
    "description": "Payment for invoice INV2024001",
    "return_url": "https:\/\/example.com\/success"
}
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Example response (201):


{
    "status": "success",
    "data": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "amount": 100.5,
        "reference": "INV2024001",
        "status": "pending",
        "checkout_url": "https://paysuite.test/checkout/550e8400-e29b-41d4-a716-446655440000"
    }
}
 

Example response (422):


{
    "status": "error",
    "message": "Invalid input"
}
 

Request      

POST api/v1/payments

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

amount   numeric     

Payment amount in MZN. Example: 100.50

method   string  optional    

optional Payment method.

Must be one of:
  • credit_card
  • mpesa
  • emola
reference   string     

Unique payment reference (max: 50 characters). Example: INV2024001

description   string  optional    

optional Payment description (max: 125 characters). Example: Payment for invoice INV2024001

return_url   string  optional    

optional URL to redirect after payment completion. Example: https://example.com/success

callback_url   string  optional    

Get Payment Request

requires authentication

Returns the details of a specific payment request.

Example request:
curl --request GET \
    --get "https://paysuite.tech/api/v1/payments/01H8X9V8X9Y8Z9A8B8C8D8E8F8" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://paysuite.tech/api/v1/payments/01H8X9V8X9Y8Z9A8B8C8D8E8F8"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://paysuite.tech/api/v1/payments/01H8X9V8X9Y8Z9A8B8C8D8E8F8';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://paysuite.tech/api/v1/payments/01H8X9V8X9Y8Z9A8B8C8D8E8F8'
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (200):


{
    "status": "success",
    "data": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "amount": 100.5,
        "reference": "INV2024001",
        "status": "paid",
        "transaction": {
            "id": 1,
            "status": "completed",
            "transaction_id": "MPESA123456",
            "paid_at": "2024-02-10T10:15:00.000000Z"
        }
    }
}
 

Example response (404):


{
    "status": "error",
    "message": "Payment request not found."
}
 

Request      

GET api/v1/payments/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

ULID of the payment request. Example: 01H8X9V8X9Y8Z9A8B8C8D8E8F8

Payout Requests

APIs for managing payout requests. Payout statuses: pending, completed, failed, cancelled.

List Payouts

requires authentication

Returns a paginated list of payouts for the authenticated account.

Example request:
curl --request GET \
    --get "https://paysuite.tech/api/v1/payouts?page=1&limit=15" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"page\": 11,
    \"limit\": 12
}"
const url = new URL(
    "https://paysuite.tech/api/v1/payouts"
);

const params = {
    "page": "1",
    "limit": "15",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "page": 11,
    "limit": 12
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://paysuite.tech/api/v1/payouts';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'page' => '1',
            'limit' => '15',
        ],
        'json' => [
            'page' => 11,
            'limit' => 12,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://paysuite.tech/api/v1/payouts'
payload = {
    "page": 11,
    "limit": 12
}
params = {
  'page': '1',
  'limit': '15',
}
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, json=payload, params=params)
response.json()

Example response (200):


{
    "data": [
        {
            "id": "550e8400-e29b-41d4-a716-446655440000",
            "amount": 100.00,
            "reference": "PO123ABC456",
            "status": "pending",
            "description": "Payout request",
            "method": "mpesa",
            "beneficiary": {"phone": "841234567", "holder": "John Doe"},
            "created_at": "2024-02-28T10:00:00.000000Z"
        }
    ],
    "links": {...},
    "meta": {...}
}
 

Request      

GET api/v1/payouts

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

page   integer  optional    

Page number. Example: 1

limit   integer  optional    

Number of items per page. Example: 15

Body Parameters

page   integer  optional    

Example: 11

limit   integer  optional    

Example: 12

Get Payout

requires authentication

Returns the details of a specific payout.

Example request:
curl --request GET \
    --get "https://paysuite.tech/api/v1/payouts/01H8X9V8X9Y8Z9A8B8C8D8E8F8" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://paysuite.tech/api/v1/payouts/01H8X9V8X9Y8Z9A8B8C8D8E8F8"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://paysuite.tech/api/v1/payouts/01H8X9V8X9Y8Z9A8B8C8D8E8F8';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://paysuite.tech/api/v1/payouts/01H8X9V8X9Y8Z9A8B8C8D8E8F8'
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (200):


{
    "data": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "amount": 100,
        "reference": "PO123ABC456",
        "status": "pending",
        "description": "Payout request",
        "method": "mpesa",
        "beneficiary": {
            "phone": "841234567",
            "holder": "John Doe"
        },
        "created_at": "2024-02-28T10:00:00.000000Z"
    }
}
 

Example response (404):


{
    "message": "Payout not found."
}
 

Request      

GET api/v1/payouts/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

ULID of the payout. Example: 01H8X9V8X9Y8Z9A8B8C8D8E8F8

Refunds

APIs for managing refunds. Refund statuses: pending, processing, completed, failed, cancelled.

List Refunds

requires authentication

Returns a paginated list of refunds for the authenticated account.

Example request:
curl --request GET \
    --get "https://paysuite.tech/api/v1/refunds?page=1&limit=20" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"page\": 4,
    \"limit\": 25
}"
const url = new URL(
    "https://paysuite.tech/api/v1/refunds"
);

const params = {
    "page": "1",
    "limit": "20",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "page": 4,
    "limit": 25
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://paysuite.tech/api/v1/refunds';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'page' => '1',
            'limit' => '20',
        ],
        'json' => [
            'page' => 4,
            'limit' => 25,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://paysuite.tech/api/v1/refunds'
payload = {
    "page": 4,
    "limit": 25
}
params = {
  'page': '1',
  'limit': '20',
}
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, json=payload, params=params)
response.json()

Example response (200):


{
    "data": [
        {
            "id": "01H8X9V8X9Y8Z9A8B8C8D8E8F8",
            "payment_id": "01H8X9V8X9Y8Z9A8B8C8D8E8F8",
            "amount": 50.00,
            "status": "completed",
            "reason": "Customer requested refund"
        }
    ],
    "links": {...},
    "meta": {...}
}
 

Example response (422):


{
    "message": "The limit field must not be greater than 100."
}
 

Request      

GET api/v1/refunds

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

page   integer  optional    

Page number. Example: 1

limit   integer  optional    

Number of items per page. Example: 20

Body Parameters

page   integer  optional    

Example: 4

limit   integer  optional    

Must not be greater than 100. Example: 25

Create Refund

requires authentication

Creates a new refund request for a completed payment.

Example request:
curl --request POST \
    "https://paysuite.tech/api/v1/refunds" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"payment_id\": \"01H8X9V8X9Y8Z9A8B8C8D8E8F8\",
    \"amount\": \"50.00\",
    \"reason\": \"Customer requested refund\"
}"
const url = new URL(
    "https://paysuite.tech/api/v1/refunds"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "payment_id": "01H8X9V8X9Y8Z9A8B8C8D8E8F8",
    "amount": "50.00",
    "reason": "Customer requested refund"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://paysuite.tech/api/v1/refunds';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'payment_id' => '01H8X9V8X9Y8Z9A8B8C8D8E8F8',
            'amount' => '50.00',
            'reason' => 'Customer requested refund',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://paysuite.tech/api/v1/refunds'
payload = {
    "payment_id": "01H8X9V8X9Y8Z9A8B8C8D8E8F8",
    "amount": "50.00",
    "reason": "Customer requested refund"
}
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Example response (201):


{
    "status": "success",
    "data": {
        "id": "01H8X9V8X9Y8Z9A8B8C8D8E8F8",
        "payment_id": "01H8X9V8X9Y8Z9A8B8C8D8E8F8",
        "amount": 50,
        "status": "pending",
        "reason": "Customer requested refund"
    }
}
 

Example response (404):


{
    "status": "error",
    "message": "Payment not found."
}
 

Example response (422):


{
    "status": "error",
    "message": "Payment cannot be refunded. It must be completed and have available refund amount."
}
 

Example response (422):


{
    "status": "error",
    "message": "Refund amount cannot exceed available refund amount of {amount}."
}
 

Request      

POST api/v1/refunds

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

payment_id   ulid     

ID of the payment to refund. Example: 01H8X9V8X9Y8Z9A8B8C8D8E8F8

amount   numeric     

Refund amount (min 0.01, max 10,000,000). Example: 50.00

reason   string     

Reason for refund (max 500 characters). Example: Customer requested refund

Get Refund

requires authentication

Returns the details of a specific refund including gateway records.

Example request:
curl --request GET \
    --get "https://paysuite.tech/api/v1/refunds/laudantium" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://paysuite.tech/api/v1/refunds/laudantium"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://paysuite.tech/api/v1/refunds/laudantium';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'https://paysuite.tech/api/v1/refunds/laudantium'
headers = {
  'Authorization': 'Bearer {YOUR_AUTH_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (200):


{
    "status": "success",
    "data": {
        "id": "01H8X9V8X9Y8Z9A8B8C8D8E8F8",
        "payment_id": "01H8X9V8X9Y8Z9A8B8C8D8E8F8",
        "amount": 50.00,
        "status": "completed",
        "reason": "Customer requested refund",
        "payment": {...}
    }
}
 

Example response (404):


{
    "status": "error",
    "message": "Refund not found."
}
 

Request      

GET api/v1/refunds/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the refund. Example: laudantium

ulid   string     

ULID of the refund. Example: 01H8X9V8X9Y8Z9A8B8C8D8E8F8