Core

Webhooks

Receive real-time notifications for payment and payout events. Verify signatures and handle retries.

Webhooks notify your application when payment events occur. This is the most reliable way to confirm payments.

Webhook Events

EventWhenAction
payment.completedPayment successfulCredit wallet, fulfill order
payment.failedPayment failedUpdate order status
payout.completedPayout sentMark payout as complete
payout.failedPayout failedRefund wallet, notify user
payout.reversedPayout reversedRefund wallet, investigate

Webhook Payload

Example payload for a completed payment:

json
{
  "id": "evt_abc123",
  "type": "payment.completed",
  "api_version": "2025-01-15",
  "created_at": "2025-01-15T14:20:00Z",
  "data": {
    "reference": "snippe_ref_abc123",
    "status": "completed",
    "amount": 2000,
    "fee_amount": 50,
    "net_amount": 1950,
    "completed_at": "2025-01-15T14:20:00Z"
  }
}

Verify Webhook Signatures

CamelPay signs each webhook with HMAC-SHA256. The signature is sent in the X-Snippe-Signature header.

Always verify webhook signatures to prevent spoofed requests. Use the raw request body and constant-time comparison.
import hmac
import hashlib

def verify_webhook_signature(raw_body, timestamp, signature, secret):
    message = f"{timestamp}.{raw_body.decode('utf-8')}"
    expected = hmac.new(
        secret.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Handle Events

Return 200 OK immediately and process events asynchronously:

python
from flask import Flask, request, jsonify
import hmac
import hashlib
import json

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret_here"

@app.route('/webhooks/camelpay', methods=['POST'])
def handle_webhook():
    # 1. Verify signature
    timestamp = request.headers.get('X-Snippe-Timestamp', '')
    signature = request.headers.get('X-Snippe-Signature', '')

    raw_body = request.data
    message = f"{timestamp}.{raw_body.decode('utf-8')}"
    expected = hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return jsonify({'error': 'Invalid signature'}), 401

    # 2. Parse and process event
    event = json.loads(raw_body)
    event_type = event['type']
    event_data = event['data']

    if event_type == 'payment.completed':
        process_completed_payment(event_data)
    elif event_type == 'payment.failed':
        process_failed_payment(event_data)
    elif event_type == 'payout.completed':
        process_completed_payout(event_data)

    # 3. Return 200 immediately
    return jsonify({'received': True}), 200

Idempotency

Webhooks may be delivered multiple times. CamelPay guarantees at-least-once delivery. Deduplicate by storing processed event IDs:

python
processed_events = set()

def handle_webhook(event):
    event_id = event['id']

    if event_id in processed_events:
        return {'status': 'already_processed'}

    process_event(event)
    processed_events.add(event_id)
    return {'status': 'processed'}