Skip to content

Webhooks

Webhooks allow you to receive real-time notifications when events occur in your TreasuryPath account. Instead of polling the API for changes, webhooks push event data to your application as soon as something happens.

Overview

TreasuryPath webhooks are HTTP POST requests sent to URLs you specify. When an event occurs (like a payment completion or account update), we'll send a webhook payload to your endpoint with details about what happened.

Setting Up Webhooks

1. Create a Webhook Endpoint

You can create webhook endpoints through the TreasuryPath dashboard or via API:

curl -X POST https://api.treasurypath.com/api/v1/webhook_endpoints \
  -H "Authorization: Bearer {jwt_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/treasurypath",
    "events": ["PaymentEvents::CompletedEvent", "PaymentEvents::FailedEvent", "BankAccountEvents::CreatedEvent"]
  }'

2. Handle Webhook Events

Your endpoint should:

  • Respond with a 200 status code to acknowledge receipt
  • Process events idempotently (handle duplicate events gracefully)
  • Verify the webhook signature for security

Available Events

TreasuryPath sends webhooks for various events organized by category:

Payment Events

  • PaymentEvents::CompletedEvent - Triggered when a payment is completed
  • PaymentEvents::FailedEvent - Triggered when a payment is failed
  • PaymentEvents::CancelledEvent - Triggered when a payment is cancelled
  • PaymentEvents::DebitInitiatedEvent - If payment has a payment order with direction debit, this event is triggered

Bank Account Events

  • BankAccountEvents::CreatedEvent - Triggered when a bank account is created
  • BankAccountEvents::UpdatedEvent - Triggered when a bank account is updated
  • BankAccountEvents::DeletedEvent - Triggered when a bank account is deleted

Payment Profile Events

  • PaymentProfileEvents::CreatedEvent - Triggered when a payment profile is created
  • PaymentProfileEvents::FailedEvent - Triggered when a payment profile becomes unable to be used for payments

Multi-Currency Account Events

  • MultiCurrencyAccountEvents::StatusChangedEvent - Triggered when a multi-currency account status changes during the feature enablement lifecycle

This event is sent when an entity's multi-currency account transitions between statuses:

  • CREATEDSUBMITTED - User completed onboarding, submission is under review
  • SUBMITTEDACTIVE - Account approved and ready for use
  • SUBMITTEDACTION_REQUIRED - Additional information needed from the user
  • ACTION_REQUIREDSUBMITTED - User provided requested information, back under review

Webhook Payload

All webhook payloads include these common fields:

{
  "event": "PaymentEvents::CompletedEvent",
  "timestamp": "2025-01-15T10:30:00Z",
  "company_id": "Z2lkOi8vd2FsbGV0LWFwcC9Db21wYW55LzE",
  "resource_id": "Z2lkOi8vd2FsbGV0LWFwcC9QYXltZW50SW5zdHJ1Y3Rpb24vNQ",
  "actor_id": "Z2lkOi8vd2FsbGV0LWFwcC9BcGlLZXkvMQ",
  "actor_type": "ApiCredential"
}

Common Fields:

  • event: The event type that triggered the webhook
  • timestamp: ISO 8601 timestamp when the event occurred
  • company_id: ID of the company associated with this event
  • resource_id: ID of the resource that triggered the event
  • actor_id: ID of the entity that initiated the action, or null for system-generated events
  • actor_type: Type of actor - User (regular user), ApiCredential (API-triggered action), or null (system-generated)

Note: Some events include additional event-specific fields beyond these common fields to provide complete context about the event.

Security

Webhook Signatures

TreasuryPath signs all webhook payloads with HMAC-SHA256 using your webhook endpoint's unique secret. Each webhook request includes a TreasuryPath-Signature header that you should verify to ensure the webhook is authentic and hasn't been tampered with.

Signature Header Format

TreasuryPath-Signature: sha256=1234567890abcdef...

Verification Examples

import hmac
import hashlib

def verify_webhook_signature(payload, signature_header, webhook_secret):
    signature = signature_header.replace('sha256=', '')
    expected = hmac.new(
        webhook_secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, signature)

@app.route('/webhooks/treasurypath', methods=['POST'])
def handle_webhook():
    payload = request.data.decode('utf-8')
    signature_header = request.headers.get('TreasuryPath-Signature')
    webhook_secret = 'your_webhook_secret_from_endpoint_creation'

    if verify_webhook_signature(payload, signature_header, webhook_secret):
        event = json.loads(payload)
        process_webhook(event)
        return '', 200
    else:
        return 'Invalid signature', 401
const crypto = require('crypto');

function verifyWebhookSignature(payload, signatureHeader, webhookSecret) {
    const signature = signatureHeader.replace('sha256=', '');
    const expected = crypto
        .createHmac('sha256', webhookSecret)
        .update(payload, 'utf8')
        .digest('hex');

    return crypto.timingSafeEqual(
        Buffer.from(expected, 'hex'),
        Buffer.from(signature, 'hex')
    );
}

app.post('/webhooks/treasurypath',
    express.raw({ type: 'application/json' }),
    (req, res) => {
        const payload = req.body.toString('utf8');
        const signatureHeader = req.headers['treasurypath-signature'];
        const webhookSecret = 'your_webhook_secret_from_endpoint_creation';

        if (verifyWebhookSignature(payload, signatureHeader, webhookSecret)) {
            const event = JSON.parse(payload);
            processWebhook(event);
            res.status(200).send('OK');
        } else {
            res.status(401).send('Invalid signature');
        }
    }
);
require 'openssl'

def verify_webhook_signature(payload, signature_header, webhook_secret)
  signature = signature_header.gsub(/^sha256=/, '')
  expected = OpenSSL::HMAC.hexdigest('sha256', webhook_secret, payload)
  ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end

class WebhooksController < ApplicationController
  def treasurypath
    payload = request.raw_post
    signature_header = request.headers['TreasuryPath-Signature']
    webhook_secret = 'your_webhook_secret_from_endpoint_creation'

    if verify_webhook_signature(payload, signature_header, webhook_secret)
      event = JSON.parse(payload)
      process_webhook(event)
      head :ok
    else
      head :unauthorized
    end
  end
end

Webhook Secret Management

Important: Your webhook secret is only provided once when you create a webhook endpoint. Store it securely and never expose it in client-side code or logs.

  • Secure Storage: Store webhook secrets in environment variables or secure configuration management
  • Secret Rotation: If your secret is compromised, delete and recreate the webhook endpoint to get a new secret
  • Access Control: Limit access to webhook secrets to only necessary systems and personnel

Best Practices

  • Use HTTPS: Always use HTTPS URLs for webhook endpoints
  • Verify Signatures: Always verify webhook signatures before processing events
  • Handle Retries: TreasuryPath will retry failed webhooks with exponential backoff
  • Be Idempotent: Process the same event multiple times safely using event IDs
  • Respond Quickly: Return a 200 status code within 30 seconds
  • Log Appropriately: Log webhook events for debugging, but never log the webhook secret
  • Handle Missing Headers: Always check for the presence of the signature header before processing

Testing Webhooks

During development, you can:

  1. Use ngrok to expose local endpoints:

    ngrok http 3000
    # Use the ngrok URL as your webhook endpoint
    

  2. Check webhook logs in your TreasuryPath dashboard to debug delivery issues

  3. Use webhook testing tools like RequestBin or Webhook.site for initial testing

Error Handling

If your endpoint returns a non-200 status code, TreasuryPath will retry the webhook:

  • Immediate retry after 1 minute
  • Exponential backoff with retries at 5, 25, 125 minutes
  • Maximum of 5 retry attempts

After all retries fail, the webhook is marked as failed and won't be retried automatically.

Managing Webhooks

You can manage your webhooks through:

  • Dashboard: View, edit, and test webhooks in the TreasuryPath dashboard
  • API: Use the Webhook Endpoints API to programmatically manage webhooks
  • Logs: View delivery logs and debug failed webhooks

For API reference, see the Webhook Endpoints API documentation.

Next Steps