Learn how to receive webhook notifications from Payment Hub
Payment Hub sends webhook notifications when invoices are paid or expire. Webhooks are configured at the store level and use HMAC-SHA256 signatures for security.
Two types of POST requests are sent to your webhook URL:
To receive webhooks, you must first create a store with webhook configuration. This store can be associated with multiple invoices.
curl -X POST https://payment-hub.paytaca.com/api/stores/ \\
-H "Content-Type: application/json" \\
-d '{
"name": "My Store",
"wallet_hash": "abc123...",
"webhook_url": "https://example.com/webhook",
"redirect_url": "https://example.com/redirect",
"webhook_secret_key": "my-super-secret-key-123"
}'
Response:
{
"success": true,
"store_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Store",
"wallet_hash": "abc123..."
}
Note: The webhook_secret_key is used to generate HMAC-SHA256 signatures for webhook verification. Keep it secure and never share it publicly.
When creating an invoice, include the store_id
to associate it with your store's webhook configuration.
{
"recipients": [
{
"amount": 5,
"address": "bitcoincash:qpgue6yr2ha2rymjhjg384lvcwpk5hmqvyjem58nlr",
"description": "Payment for goods or services"
}
],
"currency": "USD",
"memo": "Test Order #101",
"store_id": "550e8400-e29b-41d4-a716-446655440000"
}
When you include a store_id
in your invoice creation request, Payment Hub will automatically use the store's webhook URL and secret key for all webhook notifications related to that invoice.
Payment Hub uses HMAC-SHA256 signatures to ensure webhook authenticity. This is a standard cryptographic method that's available in all programming languages.
Every webhook request includes a signature header:
X-Webhook-Signature: sha256=a1b2c3d4e5f6...
import hmac
import hashlib
import json
def verify_webhook_signature(payload, signature, secret_key):
# Extract signature value
signature_value = signature.split('=')[1]
# Convert payload to JSON string
payload_str = json.dumps(payload, separators=(',', ':'))
# Compute expected signature
computed = hmac.new(
secret_key.encode('utf-8'),
payload_str.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Compare securely
return hmac.compare_digest(computed, signature_value)
# Usage
if verify_webhook_signature(payload, signature, "my-secret-key"):
print("Signature verified!")
else:
print("Invalid signature!")
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secretKey) {
// Extract signature value
const signatureValue = signature.split('=')[1];
// Convert payload to JSON string
const payloadStr = JSON.stringify(payload);
// Compute expected signature
const computed = crypto
.createHmac('sha256', secretKey)
.update(payloadStr)
.digest('hex');
// Compare securely
return crypto.timingSafeEqual(
Buffer.from(computed, 'hex'),
Buffer.from(signatureValue, 'hex')
);
}
// Usage
if (verifyWebhookSignature(payload, signature, "my-secret-key")) {
console.log("Signature verified!");
} else {
console.log("Invalid signature!");
}
<?php
function verifyWebhookSignature($payload, $signature, $secretKey) {
// Extract signature value
$signatureValue = explode('=', $signature)[1];
// Convert payload to JSON string
$payloadStr = json_encode($payload);
// Compute expected signature
$computed = hash_hmac('sha256', $payloadStr, $secretKey);
// Compare securely
return hash_equals($computed, $signatureValue);
}
// Usage
if (verifyWebhookSignature($payload, $signature, "my-secret-key")) {
echo "Signature verified!";
} else {
echo "Invalid signature!";
}
A POST request is sent to your webhook URL when an invoice payment is completed successfully.
POST /webhook HTTP/1.1
Host: example.com
Content-Type: application/json
X-Webhook-Signature: sha256=a1b2c3d4e5f6...
{
"invoice_id": "e876ed403c2e40199b33dfe3f8027905",
"status": "paid",
"currency": "USD",
"currency_amount": "0.51000000",
"bch_amount": "0.00001786",
"fee": "0.00001000",
"recipients": [
{
"address": "bitcoincash:qpsc03ratt4y9v4g25smt3avf3rcnzcudc86s7dr0p",
"currency_amount": "0.2244",
"bch_amount": "0.00000786",
"description": "Payment for goods or services"
},
{
"address": "bitcoincash:qq0kxy0ph75t7wwnsjx79zn54f594cl4uvsg8jxrnu",
"currency_amount": "0.2855",
"bch_amount": "0.00001",
"description": "Paytaca fee"
}
],
"date_paid": "2025-07-09T09:22:52.512331+00:00",
"transaction_id": "199cd87d160a1c497bc8ea54f278fa1b8a34e685e8350e0c7cfa4303da82ffc9"
}
A POST request is sent to your webhook URL when an invoice expires without being paid.
POST /webhook HTTP/1.1
Host: example.com
Content-Type: application/json
X-Webhook-Signature: sha256=a1b2c3d4e5f6...
{
"invoice_id": "e876ed403c2e40199b33dfe3f8027905",
"status": "expired"
}
You can test your webhook implementation using tools like ngrok or webhook.site:
npm install -g ngrok
python manage.py runserver
ngrok http 8000
If you need assistance with webhook implementation or have questions about the API, please contact our support team.