MAA INVEST Pay Docs
API reference

Signing requests

How to compute the HMAC-SHA256 signature, with PHP, Node.js and Python examples.

The canonical string#

Take the eleven values below exactly as you will send them (we trim leading and trailing spaces, so send them trimmed), join them with a newline (\n) in this order, and compute HMAC-SHA256 with your API secret. Send the lowercase hex digest as signature.

merchant_id, invoice_id, amount, currency, description, return_url, cancel_url, notify_url, customer_phone, customer_email, timestamp

Empty optional values still take their place in the list (an empty line). Sign the amount as the same string you post: "1500.00" and "1500" produce different signatures.

Code examples#

PHP: build the auto-submitting form#

<?php
// Run on YOUR server. Use the TEST Merchant ID/secret in the sandbox, the live ones in production.
$MERCHANT_ID = 'MRT1A2B3C4D';
$API_SECRET  = 'sk_test_...';
$BASE        = 'https://maainvest.co.ke';

$fields = [
  'merchant_id' => $MERCHANT_ID,
  'invoice_id'  => 'ORD-1001',                       // your order id, new for every test
  'amount'      => '1500.00',
  'currency'    => 'KES',
  'description' => 'Order #1001',
  'return_url'  => 'https://shop.example/thanks',
  'cancel_url'  => 'https://shop.example/cart',
  'notify_url'  => 'https://shop.example/maainvest-webhook.php',
  'customer_phone' => '',                           // or '0712345678'
  'customer_email' => '',
  'timestamp'   => (string)time(),
];
$order = ['merchant_id','invoice_id','amount','currency','description','return_url',
          'cancel_url','notify_url','customer_phone','customer_email','timestamp'];
$canonical = implode("\n", array_map(fn($k) => $fields[$k], $order));
$fields['signature'] = hash_hmac('sha256', $canonical, $API_SECRET);
?>
<form id="pay" method="post" action="<?= $BASE ?>/pay/start.php">
<?php foreach ($fields as $k => $v): ?>
  <input type="hidden" name="<?= htmlspecialchars($k) ?>" value="<?= htmlspecialchars($v) ?>">
<?php endforeach; ?>
</form>
<script>document.getElementById('pay').submit();</script>

Node.js#

const crypto = require('crypto');
const order = ['merchant_id','invoice_id','amount','currency','description','return_url',
               'cancel_url','notify_url','customer_phone','customer_email','timestamp'];
const fields = {
  merchant_id: process.env.MAA_MERCHANT_ID, invoice_id: 'ORD-1001', amount: '1500.00', currency: 'KES',
  description: 'Order #1001', return_url: 'https://shop.example/thanks', cancel_url: 'https://shop.example/cart',
  notify_url: 'https://shop.example/maainvest-webhook', customer_phone: '', customer_email: '',
  timestamp: String(Math.floor(Date.now() / 1000)),
};
const canonical = order.map(k => fields[k]).join('\n');
fields.signature = crypto.createHmac('sha256', process.env.MAA_API_SECRET).update(canonical).digest('hex');
// Render an auto-submitting <form method="post" action="https://maainvest.co.ke/pay/start.php"> with these fields.

Python#

import hmac, hashlib, time
order = ['merchant_id','invoice_id','amount','currency','description','return_url',
         'cancel_url','notify_url','customer_phone','customer_email','timestamp']
fields = {
  'merchant_id': MERCHANT_ID, 'invoice_id': 'ORD-1001', 'amount': '1500.00', 'currency': 'KES',
  'description': 'Order #1001', 'return_url': 'https://shop.example/thanks', 'cancel_url': 'https://shop.example/cart',
  'notify_url': 'https://shop.example/maainvest-webhook', 'customer_phone': '', 'customer_email': '',
  'timestamp': str(int(time.time())),
}
canonical = '\n'.join(fields[k] for k in order)
fields['signature'] = hmac.new(API_SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()
# POST fields (form-encoded) from the customer's browser to https://maainvest.co.ke/pay/start.php

If you get “could not verify”#

Most common mistake: “We could not verify this payment request.” means the signature did not match. Check the field order, that empty fields are included as empty lines, that you signed the same amount string you posted, that you used the secret that matches the Merchant ID (test with test, live with live) and that your server clock is correct.
Stuck on an integration? Open a ticket from the Support page in your account and include your invoice_id, the time, and the HTTP status you returned.