This guide will walk you through integrating Monato's Cash API into your application. You'll learn how to configure webhooks and perform operations using your provided API credentials.
- Contact our customer success team to obtain your API credentials (
api_keyandapi_secret) - A webhook endpoint to receive status notifications
- Basic understanding of REST APIs and HMAC authentication
Note: Clients are created by our customer success team. Once your client account is set up, you'll receive your unique
api_keyandapi_secretfor API authentication.
Set up webhook endpoints to receive real-time notifications about operations status changes.
Before making the request, you need to generate an HMAC-SHA256 signature. Select your programming language:
JavaScript/Node.js
const crypto = require('crypto');
const timestamp = Math.floor(Date.now() / 1000).toString();
const requestBody = JSON.stringify({
endpoint_url: "https://your-webhook-endpoint.com/webhooks"
});
const payload = `${timestamp}.${requestBody}`;
const apiSecret = "your_64_character_api_secret_here";
const signature = crypto
.createHmac('sha256', apiSecret)
.update(payload)
.digest('hex');Python
import hmac
import hashlib
import json
import time
timestamp = str(int(time.time()))
request_body = json.dumps({
"endpoint_url": "https://your-webhook-endpoint.com/webhooks"
})
payload = f"{timestamp}.{request_body}"
api_secret = "your_64_character_api_secret_here"
signature = hmac.new(
api_secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()PHP
<?php
$timestamp = time();
$requestBody = json_encode([
'endpoint_url' => 'https://your-webhook-endpoint.com/webhooks'
]);
$payload = $timestamp . '.' . $requestBody;
$apiSecret = 'your_64_character_api_secret_here';
$signature = hash_hmac('sha256', $payload, $apiSecret);
?>Ruby
require 'openssl'
require 'json'
require 'time'
timestamp = Time.now.to_i.to_s
request_body = {
endpoint_url: "https://your-webhook-endpoint.com/webhooks"
}.to_json
payload = "#{timestamp}.#{request_body}"
api_secret = "your_64_character_api_secret_here"
signature = OpenSSL::HMAC.hexdigest('SHA256', api_secret, payload)Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import com.fasterxml.jackson.databind.ObjectMapper;
long timestamp = Instant.now().getEpochSecond();
String requestBody = new ObjectMapper().writeValueAsString(
Map.of("endpoint_url", "https://your-webhook-endpoint.com/webhooks")
);
String payload = timestamp + "." + requestBody;
String apiSecret = "your_64_character_api_secret_here";
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(apiSecret.getBytes(), "HmacSHA256");
mac.init(secretKeySpec);
String signature = bytesToHex(mac.doFinal(payload.getBytes()));
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}C#
using System;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string requestBody = JsonConvert.SerializeObject(new {
endpoint_url = "https://your-webhook-endpoint.com/webhooks"
});
string payload = $"{timestamp}.{requestBody}";
string apiSecret = "your_64_character_api_secret_here";
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(apiSecret)))
{
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
string signature = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"time"
)
type RequestBody struct {
EndpointURL string `json:"endpoint_url"`
}
func main() {
timestamp := fmt.Sprintf("%d", time.Now().Unix())
requestBody := RequestBody{
EndpointURL: "https://your-webhook-endpoint.com/webhooks",
}
requestBodyJSON, _ := json.Marshal(requestBody)
payload := fmt.Sprintf("%s.%s", timestamp, string(requestBodyJSON))
apiSecret := "your_64_character_api_secret_here"
h := hmac.New(sha256.New, []byte(apiSecret))
h.Write([]byte(payload))
signature := hex.EncodeToString(h.Sum(nil))
}curl -X POST "https://your-domain.com/api/v1/cash/webhooks" \
-H "Content-Type: application/json" \
-H "X-Client-Id: a1b2c3d4e5f6g7h8" \
-H "X-Signature: abc123def456..." \
-H "X-Timestamp: 1705312200" \
-d '{
"endpoint_url": "https://your-webhook-endpoint.com/webhooks"
}'{
"event": "webhook.created",
"id": 456,
"endpoint_url": "https://your-webhook-endpoint.com/webhooks",
"secret_token": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6",
"created_at": "2025-01-15T10:30:00Z"
}Note: The webhook will be automatically tested and only activated if the test succeeds.
After configuring your webhook, our system will automatically send a test request to verify your endpoint is working correctly. Your endpoint should respond with HTTP 200-299 for the webhook to be activated.
POST https://your-webhook-endpoint.com/webhooks
Content-Type: application/json
X-Webhook-Timestamp: 1705312200
X-Webhook-Signature: abc123def456...
{
"event": "webhook.activation",
"processed_at": "2025-01-15T10:30:00Z"
}The activation request includes a signature that you should verify using your webhook's secret_token. Select your programming language:
JavaScript/Node.js
const crypto = require('crypto');
function verifyActivationSignature(payload, signature, secretToken) {
const expectedSignature = crypto
.createHmac('sha256', secretToken)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
// Example usage
const payload = JSON.stringify({
"event": "webhook.activation",
"processed_at": "2025-01-15T10:30:00Z"
});
const isValid = verifyActivationSignature(
payload,
"abc123def456...",
"your_webhook_secret_token"
);Python
import hmac
import hashlib
import json
def verify_activation_signature(payload, signature, secret_token):
expected_signature = hmac.new(
secret_token.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
# Example usage
payload = json.dumps({
"event": "webhook.activation",
"processed_at": "2025-01-15T10:30:00Z"
})
is_valid = verify_activation_signature(
payload,
"abc123def456...",
"your_webhook_secret_token"
)PHP
<?php
function verifyActivationSignature($payload, $signature, $secretToken) {
$expectedSignature = hash_hmac('sha256', $payload, $secretToken);
return hash_equals($expectedSignature, $signature);
}
// Example usage
$payload = json_encode([
'event' => 'webhook.activation',
'processed_at' => '2025-01-15T10:30:00Z'
]);
$isValid = verifyActivationSignature(
$payload,
'abc123def456...',
'your_webhook_secret_token'
);
?>Ruby
require 'openssl'
require 'json'
def verify_activation_signature(payload, signature, secret_token)
expected_signature = OpenSSL::HMAC.hexdigest('SHA256', secret_token, payload)
ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
end
# Example usage
payload = {
event: "webhook.activation",
processed_at: "2025-01-15T10:30:00Z"
}.to_json
is_valid = verify_activation_signature(
payload,
"abc123def456...",
"your_webhook_secret_token"
)Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public boolean verifyActivationSignature(String payload, String signature, String secretToken) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretToken.getBytes(), "HmacSHA256");
mac.init(secretKeySpec);
byte[] expectedSignature = mac.doFinal(payload.getBytes());
byte[] providedSignature = hexStringToByteArray(signature);
return Arrays.equals(expectedSignature, providedSignature);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
return false;
}
}
private byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}C#
using System;
using System.Security.Cryptography;
using System.Text;
public bool VerifyActivationSignature(string payload, string signature, string secretToken)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretToken)))
{
byte[] expectedSignature = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
byte[] providedSignature = HexStringToByteArray(signature);
return CryptographicOperations.FixedTimeEquals(expectedSignature, providedSignature);
}
}
private byte[] HexStringToByteArray(string hex)
{
int numberChars = hex.Length;
byte[] bytes = new byte[numberChars / 2];
for (int i = 0; i < numberChars; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func verifyActivationSignature(payload, signature, secretToken string) bool {
h := hmac.New(sha256.New, []byte(secretToken))
h.Write([]byte(payload))
expectedSignature := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
// Example usage
payload := `{"event":"webhook.activation","processed_at":"2025-01-15T10:30:00Z"}`
isValid := verifyActivationSignature(
payload,
"abc123def456...",
"your_webhook_secret_token",
)Your endpoint should return HTTP 200-299 to activate the webhook:
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "success",
"message": "Webhook activated successfully"
}Now you can create cash-in and cash-out operations for your users.
curl -X POST "https://your-domain.com/api/v1/cash/cash_in" \
-H "Content-Type: application/json" \
-H "X-Client-Id: a1b2c3d4e5f6g7h8" \
-H "X-Signature: abc123def456..." \
-H "X-Timestamp: 1705312200" \
-d '{
"amount": 500.00,
"external_user_id": "USER123456",
"document_type": "INE",
"document_id": "1234567890123",
"phone": "5512345678"
}'curl -X POST "https://your-domain.com/api/v1/cash/cash_out" \
-H "Content-Type: application/json" \
-H "X-Client-Id: a1b2c3d4e5f6g7h8" \
-H "X-Signature: abc123def456..." \
-H "X-Timestamp: 1705312200" \
-d '{
"amount": 250.00,
"external_user_id": "USER789012",
"document_type": "CURP",
"document_id": "ABCD123456HMNMNL01",
"phone": "5587654321"
}'{
"operation_id": 123,
"kind": "cash_in",
"reference": "10511175512161627448",
"status": "unpaid",
"transaction_id": "FMXdbnBuiw2SHqSyfzSkqN71q",
"amount": 500.0,
"created_at": "2025-01-15T10:30:00Z",
"expire_at": "2025-01-18T10:30:00Z"
}Important: Store the reference number. Users will need it to complete the operation at physical locations.
Check the status of operations using the reference number.
curl -X GET "https://your-domain.com/api/v1/cash/consult?reference=10511175512161627448" \
-H "X-Client-Id: a1b2c3d4e5f6g7h8" \
-H "X-Signature: abc123def456..." \
-H "X-Timestamp: 1705312200"{
"operation_id": 123,
"kind": "cash_in",
"reference": "10511175512161627448",
"status": "paid",
"transaction_id": "FMXdbnBuiw2SHqSyfzSkqN71q",
"amount": 500.0,
"created_at": "2025-01-15T10:30:00Z",
"expire_at": "2025-01-18T10:30:00Z"
}Your webhook endpoint will receive notifications when operations change status. These are incoming webhooks that Monato sends to your server - you don't make API calls to receive them.
Monato will send your webhook endpoint two types of events:
- Webhook Activation Test - Sent when you first configure a webhook to verify your endpoint works
- Operation Status Updates - Sent when cash operations change status (paid, expired, reversed)
{
"event": "webhook.paid.success",
"operation_id": 123,
"external_user_id": "USER789012",
"type": "cash_in",
"status": "paid",
"amount": 500,
"reference": "10511175512161627448",
"processed_at": "2025-01-15T10:30:00Z"
}Always verify webhook signatures to ensure authenticity. Select your programming language:
JavaScript/Node.js
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}Python
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)PHP
<?php
function verifyWebhookSignature($payload, $signature, $secret) {
$expectedSignature = hash_hmac('sha256', $payload, $secret);
return hash_equals($expectedSignature, $signature);
}
?>Ruby
require 'openssl'
def verify_webhook_signature(payload, signature, secret)
expected_signature = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
endJava
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public boolean verifyWebhookSignature(String payload, String signature, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
mac.init(secretKeySpec);
byte[] expectedSignature = mac.doFinal(payload.getBytes());
byte[] providedSignature = hexStringToByteArray(signature);
return Arrays.equals(expectedSignature, providedSignature);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
return false;
}
}
private byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}C#
using System;
using System.Security.Cryptography;
using System.Text;
public bool VerifyWebhookSignature(string payload, string signature, string secret)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
{
byte[] expectedSignature = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
byte[] providedSignature = HexStringToByteArray(signature);
return CryptographicOperations.FixedTimeEquals(expectedSignature, providedSignature);
}
}
private byte[] HexStringToByteArray(string hex)
{
int numberChars = hex.Length;
byte[] bytes = new byte[numberChars / 2];
for (int i = 0; i < numberChars; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func verifyWebhookSignature(payload, signature, secret string) bool {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(payload))
expectedSignature := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}- Cash-In: Maximum amount varies by physical location
- Cash-Out: Maximum amount varies by physical location
- Daily Limit: Maximum 5 operations per user per day
- Reset Time: Limits reset at midnight (Mexico City timezone)
- INE: 13 digits (e.g.,
1234567890123) - CURP: 18 characters (e.g.,
ABCD123456HMNMNL01) - RFC: 10-13 characters (e.g.,
ABCD123456ABC) - Phone: Exactly 10 digits (e.g.,
5512345678)
- Cash-In Operations: Expire 3 days after creation.
- Open References: Expire based on the custom date you set during creation.
- Cash-Out Operations: Expire 60 minutes after creation by default, but can be configured by the client to last longer
- Expired operations cannot be completed
- Monitor
expire_attimestamp in responses
The API returns specific error codes for different scenarios:
| Error Code | Description | Resolution |
|---|---|---|
60 | Invalid parameters | Check request parameters and validation rules |
61 | Invalid document identifier | Verify document type and ID format |
13 | Invalid reference length | Reference must be exactly 20 digits |
14 | Invalid reference | Reference not found in system |
64 | Operation not found | Verify reference number exists |
- Field delimiter: pipe character
|(vertical bar). - Order: exactly one header row first, zero or more body rows, exactly one footer row last.
Each logical row is a single text line. Fields must not contain the delimiter unless an escaping rule is agreed separately (default: no pipe characters inside fields).
The header row marks the beginning of the reconciliation file.
Format
h- The line consists of the single character
h(lowercase) with no additional fields.
Each body row represents a single reconciled transaction (cash-in or cash-out).
Format
b|<payment_id>|<type>|<amount>|<reference>|<operation_date>|<currency>| Position | Field | Description | Format / notes |
|---|---|---|---|
| 1 | Row marker | Literal b (lowercase). | Fixed. |
| 2 | payment_id | Unique identifier of the payment / transaction. | UUID (canonical string form, lowercase hex with hyphens, e.g. 42dd06f4-c53d-4951-95ba-0ce5e8c35f30). |
| 3 | type | Operation kind. | CASH_IN for cash-in, CASH_OUT for cash-out. |
| 4 | amount | Transaction amount. | Decimal, dot as separator, two fractional digits recommended (e.g. 2495.00, 123.00). |
| 5 | reference | Reference or destination account for the movement. | String (e.g. 20-digit reference). No spaces unless part of the agreed reference format. |
| 6 | operation_date | Operation date. | ddmmyyyy (day two digits, month two digits, year four digits). Example: 25 March 2026 → 25032026. |
| 7 | currency | Transaction currency. | ISO 4217 alphabetic code (e.g. MXN). |
Example body lines
b|42dd06f4-c53d-4951-95ba-0ce5e8c35f30|CASH_OUT|2495.00|20511446630069045555|25032026|MXN
b|49154917-b77e-4af0-8155-eec9b45df262|CASH_OUT|123.00|20511446636424426786|25032026|MXN
b|104ca5a9-277e-4d6b-b5f8-31a68a9a5270|CASH_OUT|948.00|20511446637894288912|25032026|MXNCash-in rows use the same structure with CASH_IN (or the agreed cash_in literal) in the type field.
The footer row carries totals used to validate the file against the body rows.
Format
f|<total_payments>|<total_amount>| Position | Field | Description | Format / notes |
|---|---|---|---|
| 1 | Row marker | Literal f (lowercase). | Fixed. |
| 2 | total_payments | Count of body rows in the file. | Non-negative integer (e.g. 3). |
| 3 | total_amount | Sum of all body row amounts. | Decimal, same rules as body amount (e.g. 3566.00). Must equal the arithmetic sum of the amount fields of all b rows (to the agreed rounding rule). |
Example
f|3|3566.00- Single format: Cash-in and cash-out share the same file layout, delimiter, and columns.
- Discrimination: The body field
typeindicates whether the row is cash-in or cash-out. - Mixed files: A file may contain both
CASH_INandCASH_OUTrows when batching a calendar period or settlement window.
Recommended pattern (adjust PREFIX with your integration client name):
{PREFIX}_CASH_{ddmmyyyy}.txt| Part | Meaning |
|---|---|
PREFIX | Client name prefix (e.g. client short code). |
CASH | Fixed segment indicating cash reconciliation. |
ddmmyyyy | Business or settlement date in ddmmyyyy form, aligned with the reconciliation period. |
Example: ACME_CASH_25032026.txt
Some pipelines use the .csv extension for the same content; the format is still delimiter-separated text, not a comma-separated CSV. Use the extension agreed with operations.
h
b|42dd06f4-c53d-4951-95ba-0ce5e8c35f30|OUT|2495.00|20511446630069045555|25032026|MXN
b|49154917-b77e-4af0-8155-eec9b45df262|OUT|123.00|20511446636424426786|25032026|MXN
b|104ca5a9-277e-4d6b-b5f8-31a68a9a5270|OUT|948.00|20511446637894288912|25032026|MXN
f|3|3566.00Checks: total_payments = 3; total_amount = 2495.00 + 123.00 + 948.00 = 3566.00.
| Row marker | Role |
|---|---|
h | Header — identifies the start of the file. |
b | Body — one completed payment / movement. |
f | Footer — aggregate totals for validation. |
- Test Your Integration: Use the staging environment to test your implementation
- Implement Error Handling: Add proper error handling for all response codes
- Monitor Webhooks: Ensure your webhook endpoint is reliable and responds quickly
- Handle Edge Cases: Implement logic for expired operations and daily limits
Need help with your integration?
- Documentation: Browse our Cash API Reference
- Support: Contact us at engineering@monato.com
- Office Hours: Schedule a call with our technical team
Happy coding with Monato Cash API!