Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

Webhook Notifications

Webhooks enable your application to receive real-time notifications when events occur in Cashela. Instead of polling the API for updates, webhooks push data to your server immediately when a transaction status changes, payment completes, or other important events happen.


Overview

How Webhooks Work

  1. Configure your endpoint – Provide a callback URL when creating transactions
  2. Event occurs – A transaction status changes (e.g., deposit completed)
  3. Cashela sends notification – HTTP POST request to your callback URL
  4. You process and respond – Return 2xx status to acknowledge receipt
  5. Retry on failure – If we don’t receive 2xx, we retry with exponential backoff

Benefits

  • Real-time updates – Know immediately when transactions complete
  • Reduced API calls – No need to poll for status changes
  • Reliability – Automatic retries ensure delivery
  • Security – Cryptographic signatures verify authenticity

Webhook Delivery

Request Format

All webhook notifications are sent as HTTP POST requests with:

HeaderDescription
Content-Typeapplication/json
X-Cashela-SignatureHMAC-SHA256 signature of the request body
X-Cashela-EventEvent type (e.g., transaction.completed)
X-Cashela-Delivery-IdUnique identifier for this delivery attempt
X-Cashela-TimestampUnix timestamp when the webhook was sent

Retry Policy

If your endpoint doesn’t return a 2xx status code, Cashela will retry:

AttemptDelayCumulative Time
1Immediate0 seconds
25 seconds5 seconds
330 seconds35 seconds
42 minutes~2.5 minutes
515 minutes~17 minutes
61 hour~1.3 hours
74 hours~5.3 hours
824 hours~29 hours

After 8 failed attempts, the webhook is marked as failed and no further retries are made.

Tip: If you consistently miss webhooks, check your server logs, firewall settings, and SSL certificate configuration.


Signature Verification

Every webhook includes a cryptographic signature that you must verify before processing. This ensures the request originated from Cashela and wasn’t tampered with.

Signature Header

X-Cashela-Signature: sha256=5d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e

Verification Steps

  1. Extract the raw request body (before any parsing)
  2. Compute HMAC-SHA256 using your Business Secret as the key
  3. Compare your computed signature with the header value
  4. Reject the request if signatures don’t match

Implementation Examples

Node.js

const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
// Use constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Express.js middleware
app.post('/webhook/cashela', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-cashela-signature'];
const rawBody = req.body.toString();
if (!verifyWebhookSignature(rawBody, signature, process.env.CASHELA_SECRET)) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the webhook
const event = JSON.parse(rawBody);
handleWebhookEvent(event);
res.status(200).json({ received: true });
});

Python (Flask)

import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
CASHELA_SECRET = os.environ.get('CASHELA_SECRET')
def verify_signature(payload, signature, secret):
expected = 'sha256=' + hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route('/webhook/cashela', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Cashela-Signature')
raw_body = request.get_data()
if not verify_signature(raw_body, signature, CASHELA_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
event = request.get_json()
process_webhook_event(event)
return jsonify({'received': True}), 200

PHP

<?php
function verifyWebhookSignature($payload, $signature, $secret) {
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}
// Handle incoming webhook
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_CASHELA_SIGNATURE'] ?? '';
if (!verifyWebhookSignature($rawBody, $signature, CASHELA_SECRET)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
$event = json_decode($rawBody, true);
processWebhookEvent($event);
http_response_code(200);
echo json_encode(['received' => true]);
?>

Event Types

PayIn Events

EventDescription
payin.deposit.createdDeposit request created, awaiting payment
payin.deposit.pendingPayment initiated, processing
payin.deposit.completedPayment successfully received
payin.deposit.failedPayment failed or was declined
payin.deposit.expiredPayment window expired without completion
payin.deposit.refundedPayment was refunded to customer

PayOut Events

EventDescription
payout.transaction.createdPayout request created
payout.transaction.processingPayout is being processed
payout.transaction.completedFunds delivered to beneficiary
payout.transaction.failedPayout failed (insufficient funds, invalid account, etc.)
payout.transaction.cancelledPayout was cancelled

Webhook Payload Structure

Standard Payload Format

{
"id": "evt_01HJ3KBCD8E9F0G1H2I3J4K5L6",
"type": "payin.deposit.completed",
"api_version": "2024-01-01",
"created": "2024-01-15T10:30:00.000Z",
"data": {
"object": {
"id": "dep_01HJ3KBCD8E9F0G1H2I3J4K5L6",
"status": "COMPLETED",
"amount": 1500.00,
"currency": "MXN",
"payment_method": "CARD",
"external_identifier": "order_12345",
"metadata": {
"order_id": "12345",
"customer_id": "cust_789"
}
},
"previous_status": "PENDING"
},
"request_id": "req_01HJ3KBCD8E9F0G1H2I3J4K5L6"
}

Payload Fields

FieldTypeDescription
idstringUnique identifier for this event
typestringEvent type (see Event Types above)
api_versionstringAPI version used to generate the event
createdstringISO-8601 timestamp when event occurred
data.objectobjectThe resource that triggered the event
data.previous_statusstringPrevious status (for status change events)
request_idstringOriginal API request ID (if applicable)

Handling Webhooks

Best Practices

  1. Respond quickly

    Return a 2xx response as soon as you receive the webhook. Process the event asynchronously.

    app.post('/webhook', async (req, res) => {
    // Acknowledge immediately
    res.status(200).json({ received: true });
    // Process asynchronously
    queueWebhookProcessing(req.body);
    });
  2. Handle idempotency

    Webhooks may be delivered multiple times. Use the event id to deduplicate.

    async function processWebhook(event) {
    // Check if already processed
    const existing = await db.webhookEvents.findOne({ eventId: event.id });
    if (existing) {
    console.log(`Event ${event.id} already processed, skipping`);
    return;
    }
    // Process and mark as handled
    await handleEvent(event);
    await db.webhookEvents.create({ eventId: event.id, processedAt: new Date() });
    }
  3. Verify before trusting

    Always verify the signature before processing any webhook data.

  4. Handle unknown events gracefully

    function handleEvent(event) {
    switch (event.type) {
    case 'payin.deposit.completed':
    handleDepositCompleted(event.data.object);
    break;
    case 'payout.transaction.completed':
    handlePayoutCompleted(event.data.object);
    break;
    default:
    // Log but don't fail on unknown events
    console.log(`Unhandled event type: ${event.type}`);
    }
    }
  5. Implement logging

    Log all webhook activity for debugging and auditing.

    async function logWebhook(event, status) {
    await db.webhookLogs.create({
    eventId: event.id,
    eventType: event.type,
    receivedAt: new Date(),
    status: status,
    payload: JSON.stringify(event)
    });
    }

Error Recovery

If your server was down and missed webhooks:

  1. Check transaction status via API – Use the Get Transaction endpoint to fetch current status
  2. Review webhook logs – Failed deliveries are logged in your Cashela dashboard
  3. Reconcile periodically – Run daily reconciliation to catch any missed events

Security Checklist

  • HTTPS only – Never accept webhooks over plain HTTP
  • Verify signatures – Always validate X-Cashela-Signature header
  • Use constant-time comparison – Prevent timing attacks when comparing signatures
  • Protect your endpoint – Use firewall rules to limit access if possible
  • Don’t trust data blindly – Validate all data from webhooks
  • Rotate secrets – Periodically rotate your Business Secret

Testing Webhooks

Local Development

Use tools like ngrok or localtunnel to expose your local server:

Terminal window
# Install ngrok
npm install -g ngrok
# Expose local port
ngrok http 3000

Use the generated HTTPS URL as your callback URL for testing.

Webhook Simulation

In sandbox mode, you can trigger test webhooks from the Cashela dashboard to verify your implementation.


Troubleshooting

IssuePossible CauseSolution
No webhooks receivedFirewall blocking requestsAllow Cashela IP ranges
Signature verification failingUsing parsed body instead of rawUse raw request body for signature
Duplicate events processedNot checking event IDStore and check event IDs
5xx errors on your endpointSlow processingRespond immediately, process async
SSL certificate errorsInvalid or expired certUpdate SSL certificate