PLH Verification Codes

Receive verification codes directly in your application via webhook for automated payment flows.

How It Works

When a purchase requires verification, the code is automatically sent to your webhook instead of SMS.

Purchase → Verification Code → Your Webhook → Complete Payment

Retries

If your webhook fails (non-2xx response or timeout), Cardda retries up to 3 times with exponential backoff.

Setup

Contact Cardda support to enable this feature. You'll need to provide:

  • Webhook URL: Your endpoint (e.g., https://yourapp.com/api/codes)
  • Card ID: The card you want to configure

Cardda will provide you with:

  • webhook_secret: A secret key to verify webhook requests
⚠️

Important: Store the webhook_secret securely. It will only be shown once.

Webhook Payload

Cardda sends a POST request to your webhook with security headers:

POST https://yourapp.com/api/codes
Content-Type: application/json
X-Cardda-Signature: b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9
X-Cardda-Timestamp: 1644512345

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "body": "Tu codigo de verificacion es 123456",
  "vendor_card_id": "12345",
  "created_at": "2026-02-10T14:30:00.000Z"
}

Security Headers:

  • X-Cardda-Signature: HMAC-SHA256 signature of the request body
  • X-Cardda-Timestamp: Unix timestamp when the request was sent

Your webhook must:

  • Verify the signature before processing
  • Respond with 200 OK within 10 seconds

Implementation

The following example shows how to receive and verify a webhook request using Python and Flask. It validates the timestamp to prevent replay attacks, verifies the HMAC signature to ensure the request comes from Cardda, and extracts the 6-digit verification code from the message body.

import hmac
import hashlib
import time
import re
import os
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.post('/api/codes')
def receive_code():
    signature = request.headers.get('X-Cardda-Signature')
    timestamp = request.headers.get('X-Cardda-Timestamp')
    secret = os.environ['CARDDA_WEBHOOK_SECRET']
    raw_body = request.get_data(as_text=True)

    # 1. Verify timestamp (prevent replay attacks)
    try:
        if abs(time.time() - int(timestamp)) > 300:
            return jsonify({'error': 'Request too old'}), 401
    except (ValueError, TypeError):
        return jsonify({'error': 'Invalid timestamp'}), 401

    # 2. Verify HMAC signature (timestamp + body)
    signed_payload = f'{timestamp}.{raw_body}'
    expected_signature = hmac.new(
        secret.encode(), signed_payload.encode(), hashlib.sha256
    ).hexdigest()

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

    # 3. Extract and process code
    body = request.json
    match = re.search(r'\d{6}', body['body'])
    if not match:
        return jsonify({'status': 'received'}), 200

    # Add your logic here to handle the verification code
    process_code(body['vendor_card_id'], match.group())
    return jsonify({'status': 'received'}), 200
📘

Always respond 200

Respond with 200 OK after successful verification to acknowledge receipt.

Security Notes

  • Always use HTTPS endpoints (https://yourapp.com/api/codes)
  • Verify the HMAC signature on every request
  • Check timestamp to prevent replay attacks (< 5 minutes old)
  • Store your webhook_secret securely (environment variables)

Troubleshooting

Not receiving codes?

  • Verify the feature is enabled (contact support)
  • Check your webhook is publicly accessible
  • Ensure your webhook responds within 10 seconds