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 PaymentRetries
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 thewebhook_secretsecurely. 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 bodyX-Cardda-Timestamp: Unix timestamp when the request was sent
Your webhook must:
- Verify the signature before processing
- Respond with
200 OKwithin 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 200Respond 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_secretsecurely (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
Updated about 18 hours ago
