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 completedPaymentEvents::FailedEvent- Triggered when a payment is failedPaymentEvents::CancelledEvent- Triggered when a payment is cancelledPaymentEvents::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 createdBankAccountEvents::UpdatedEvent- Triggered when a bank account is updatedBankAccountEvents::DeletedEvent- Triggered when a bank account is deleted
Payment Profile Events
PaymentProfileEvents::CreatedEvent- Triggered when a payment profile is createdPaymentProfileEvents::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:
CREATED→SUBMITTED- User completed onboarding, submission is under reviewSUBMITTED→ACTIVE- Account approved and ready for useSUBMITTED→ACTION_REQUIRED- Additional information needed from the userACTION_REQUIRED→SUBMITTED- 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 webhooktimestamp: ISO 8601 timestamp when the event occurredcompany_id: ID of the company associated with this eventresource_id: ID of the resource that triggered the eventactor_id: ID of the entity that initiated the action, ornullfor system-generated eventsactor_type: Type of actor -User(regular user),ApiCredential(API-triggered action), ornull(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
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:
-
Use ngrok to expose local endpoints:
-
Check webhook logs in your TreasuryPath dashboard to debug delivery issues
-
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
- Webhooks API Reference - Complete API documentation for managing webhook endpoints
- Error Handling - Learn best practices for handling webhook failures
- API Basics - Understand API response formats and error handling
- Quick Start Guide - Set up your first payment workflow