Webhook Documentation

Learn how to receive webhook notifications from Payment Hub

← Back to Payment Hub

Overview

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:

Store Setup

To receive webhooks, you must first create a store with webhook configuration. This store can be associated with multiple invoices.

Create a Store

Create Store:

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.

Creating Invoices with Store

When creating an invoice, include the store_id to associate it with your store's webhook configuration.

Create Invoice with Store:

{
  "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.

Webhook Security (HMAC-SHA256)

Payment Hub uses HMAC-SHA256 signatures to ensure webhook authenticity. This is a standard cryptographic method that's available in all programming languages.

Signature Header

Every webhook request includes a signature header:

X-Webhook-Signature: sha256=a1b2c3d4e5f6...

Verification Examples

Python

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!")

Node.js

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

<?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!";
}

When Invoice is Paid

A POST request is sent to your webhook URL when an invoice payment is completed successfully.

Sample Request:

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"
}

Field Descriptions:

invoice_id: Unique invoice identifier
status: Always "paid" for successful payments
currency: The currency used for the invoice
currency_amount: Total amount in the original currency
bch_amount: Total BCH amount received
fee: Transaction fee in BCH
recipients: Array of payment recipients
date_paid: ISO timestamp when payment was received
transaction_id: Bitcoin Cash transaction hash

When Invoice Expires

A POST request is sent to your webhook URL when an invoice expires without being paid.

Sample Request:

POST /webhook HTTP/1.1
Host: example.com
Content-Type: application/json
X-Webhook-Signature: sha256=a1b2c3d4e5f6...

{
  "invoice_id": "e876ed403c2e40199b33dfe3f8027905",
  "status": "expired"
}

Field Descriptions:

invoice_id: Unique invoice identifier
status: Always "expired" for expired invoices

Implementation Notes

Webhook URL Requirements

  • Must be a valid HTTPS URL
  • Should respond with HTTP 200 status code
  • Should handle the webhook within 30 seconds
  • Should verify the HMAC-SHA256 signature before processing

Security Best Practices

  • Always verify the webhook signature before processing
  • Store your webhook secret key securely
  • Use HTTPS for all webhook endpoints
  • Implement idempotency to handle duplicate webhooks
  • Log webhook events for debugging and auditing

Error Handling

  • Return HTTP 200 for successful webhook processing
  • Return HTTP 4xx for invalid requests (malformed payload, invalid signature)
  • Return HTTP 5xx for server errors (temporary issues)
  • Payment Hub will retry failed webhooks up to 7 times

Testing Webhooks

You can test your webhook implementation using tools like ngrok or webhook.site:

Using webhook.site:

  1. Go to webhook.site
  2. Copy your unique webhook URL
  3. Use this URL when creating your store
  4. Create an invoice and watch for webhook notifications

Using ngrok:

  1. Install ngrok: npm install -g ngrok
  2. Start your local server: python manage.py runserver
  3. Expose your local server: ngrok http 8000
  4. Use the ngrok URL as your webhook URL

Need Help?

If you need assistance with webhook implementation or have questions about the API, please contact our support team.