Cash · Reference
Cash webhook events
Configure your webhook endpoint, pass the activation test and handle status events.
Monato sends webhooks to your endpoint when a cash operation changes status. Only one webhook can be active per client.
Webhooks are sent only for closed-reference operations. Operations with open references do not currently send status updates by webhook.
Configure your endpoint
Sign the request as described in Sign requests with HMAC. The endpoint_url must use http or https.
curl -X POST "https://dev-api.finco.lat/api/v1/cash/webhooks" \
-H "Content-Type: application/json" \
-H "X-Client-Id: 4a8a08f09d37b73795649038408b5f33" \
-H "X-Signature: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" \
-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": "644530cd9b0b431e61b8c6c656d17c77481047215a3ac66db71a7ad490397f7c",
"created_at": "2025-01-15T10:30:00Z"
}Store the secret_token. It is a 64-character hexadecimal token you use to verify webhook signatures.
Creating a new webhook deactivates any existing one after the new one is activated. See Create Webhook.
Activation test
After you configure the webhook, Monato sends a test request to your endpoint. The webhook is activated only if your endpoint responds with HTTP 200 to 299.
POST https://your-webhook-endpoint.com/webhooks
Content-Type: application/json
X-Webhook-Timestamp: 1705312200
X-Webhook-Signature: a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890
{
"event": "webhook.activation",
"processed_at": "2025-01-15T10:30:00Z"
}HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "success",
"message": "Webhook activated successfully"
}Status events
| Status | Event | Sent when |
|---|---|---|
paid |
webhook.paid.success |
The cash operation was paid |
expired |
webhook.expired.success |
The cash operation expired |
reversed |
webhook.reversed.success |
The cash operation was reversed |
{
"event": "webhook.paid.success",
"operation_id": 123,
"external_user_id": "USER123456",
"type": "cash_in",
"amount": 500,
"reference": "10511175512161627448",
"status": "paid",
"processed_at": "2025-01-15T10:30:00Z"
}| Field | Type | Description |
|---|---|---|
event |
string | webhook.paid.success, webhook.expired.success or webhook.reversed.success |
operation_id |
integer | Unique identifier for the operation |
external_user_id |
string | Your identifier for the end user |
type |
string | cash_in or cash_out |
amount |
integer | Amount in MXN |
reference |
string | 20-digit operation reference |
status |
string | New status: paid, expired or reversed |
processed_at |
string | ISO 8601 timestamp when the status was updated |
Headers
Every webhook request includes:
| Header | Description |
|---|---|
X-Webhook-Timestamp |
Unix timestamp when the webhook was sent |
X-Webhook-Signature |
HMAC-SHA256 signature, hex-encoded |
Verify the signature
Always verify the signature with your webhook’s secret_token before you trust the payload.
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')
);
}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
function verifyWebhookSignature($payload, $signature, $secret) {
$expectedSignature = hash_hmac('sha256', $payload, $secret);
return hash_equals($expectedSignature, $signature);
}
?>require 'openssl'
def verify_webhook_signature(payload, signature, secret)
expected_signature = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
endimport 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;
}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;
}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))
}Manage your webhook
| Task | Endpoint |
|---|---|
| See the active webhook | Get Active Webhook. Returns 404 if none is active. |
| Change the endpoint URL | Update Webhook. The webhook is deactivated and a new activation test is sent. It becomes active again only if the new endpoint passes the test. |
{
"id": 456,
"endpoint_url": "https://new-webhook-endpoint.com/webhooks"
}