Overview
Webhooks enable real-time event notifications between Royalti.io and your systems. This guide covers two distinct webhook systems:- Outbound Webhooks - Royalti sends event notifications TO your endpoints when events occur
- Inbound Webhooks - Your systems send status updates TO Royalti endpoints
Why Use Webhooks?
- Real-time Updates: Get notified instantly when events occur instead of polling APIs
- Automation: Trigger workflows in your systems based on Royalti events
- Efficiency: Reduce API calls and server load
- Integration: Connect Royalti with external tools (CRMs, analytics, notifications)
Outbound Webhooks (Royalti Sends to You)
Outbound webhooks allow you to receive real-time notifications when events occur in your Royalti workspace.Quick Start
1
Configure Webhook URL
Set your webhook endpoint URL via Tenant Settings API:
2
Enable Webhooks
Activate webhook delivery:
3
Subscribe to Events
Choose which events to receive (comma-separated):
4
Implement Endpoint
Create an HTTP endpoint that accepts POST requests:
Event Catalog
Royalti.io supports 20+ event types across multiple categories:Financial Events (Require Signature Validation)
These events involve sensitive financial data and automatically enforce HMAC signature validation:| Event Type | Description | Payload Highlights |
|---|---|---|
PAYMENT_COMPLETED | Payment successfully processed | amount, currency, recipientId |
PAYMENT_PROCESSING | Payment in progress | amount, status, estimatedCompletion |
PAYMENT_MADE_FAILED | Payment failed | amount, errorCode, errorMessage |
PAYMENT_REQUEST_SENT | Payment request created | requestId, amount, recipientEmail |
PAYMENT_REQUEST_APPROVED | Payment request approved | requestId, approvedBy, approvedAt |
PAYMENT_REQUEST_REJECTED | Payment request rejected | requestId, rejectedBy, reason |
PAYMENT_DELETED | Payment record deleted | paymentId, deletedBy |
REVENUE_CREATED | Revenue entry created | amount, source, period |
REVENUE_UPDATED | Revenue entry updated | revenueId, changes, previousAmount |
REVENUE_DELETED | Revenue entry deleted | revenueId, amount, reason |
EXPENSE_CREATED | Expense entry created | amount, category, description |
EXPENSE_UPDATED | Expense entry updated | expenseId, changes |
EXPENSE_DELETED | Expense entry deleted | expenseId, amount |
Catalog Events (Default Enabled)
| Event Type | Description | Payload Highlights |
|---|---|---|
ASSET_CREATED | New asset added | assetId, title, artists, mediaType |
ASSET_UPDATED | Asset modified | assetId, changes, previous |
ASSET_DELETED | Asset removed | assetId, title, deletedBy |
PRODUCT_CREATED | New product/album created | productId, title, assets, releaseDate |
PRODUCT_UPDATED | Product modified | productId, changes, previous |
PRODUCT_DELETED | Product removed | productId, title |
Roster Events (Default Enabled)
| Event Type | Description | Payload Highlights |
|---|---|---|
USER_CREATED | New user added to workspace | userId, email, role, name |
USER_UPDATED | User profile/permissions modified | userId, changes, updatedBy |
USER_DELETED | User removed from workspace | userId, email, deletedBy |
USER_INVITATION_SENT | User invited to workspace | inviteId, email, role, invitedBy |
ARTIST_CREATED | New artist profile created | artistId, name, genres |
ARTIST_UPDATED | Artist profile modified | artistId, changes |
ARTIST_DELETED | Artist profile removed | artistId, name |
Royalty Events (Require Explicit Opt-in)
High-volume events that require explicit opt-in to prevent overwhelming webhooks:| Event Type | Description | Payload Highlights |
|---|---|---|
ROYALTY_FILE_UPLOADED | Royalty file uploaded | fileId, fileName, source, uploadedBy |
ROYALTY_FILE_PROCESSED | Royalty file processing completed | fileId, recordsProcessed, totalAmount, duration |
ROYALTY_FILE_PROCESSING_FAILED | Royalty file processing failed | fileId, errorCode, errorMessage, failedAt |
Split Events (Require Signature Validation)
| Event Type | Description | Payload Highlights |
|---|---|---|
USER_ADDED_TO_SPLIT | User added to revenue split | splitId, userId, percentage, resource |
USER_REMOVED_FROM_SPLIT | User removed from split | splitId, userId, removedBy, reason |
Webhook Payload Structure
All outbound webhooks follow this standardized structure:Field Descriptions
| Field | Type | Description |
|---|---|---|
id | string | Unique webhook delivery ID (format: whd_{uuid}) |
event | string | Event type from NotificationType enum |
timestamp | ISO 8601 | When the event occurred |
version | string | Webhook payload schema version (currently “1.0”) |
tenant.id | integer | Your tenant/workspace ID |
tenant.name | string | Your workspace name |
tenant.domain | string | Your workspace domain |
source.service | string | Service that generated the event |
source.environment | string | Environment (production, staging, development) |
source.traceId | string | Request correlation ID for debugging |
data.event | object | Event metadata (ID, type, category, importance) |
data.actor | object | Who/what triggered the event |
data.resource | object | The resource affected by the event |
data.attributes | object | Event-specific data (varies by event type) |
data.previous | object | Previous state (for update events only) |
delivery.attempt | integer | Current delivery attempt (1-based) |
delivery.maxAttempts | integer | Maximum retry attempts configured |
delivery.nextRetryAt | ISO 8601 | When next retry will occur (null if no retry) |
Webhook Configuration
Configure your webhook behavior through Tenant Settings:Available Settings
| Setting Name | Type | Default | Description |
|---|---|---|---|
webhook-url | string | - | Your webhook endpoint URL (must be HTTPS) |
webhook-isActive | boolean | false | Enable/disable webhook delivery |
webhook-enabledEvents | string | - | Comma-separated list of event types or “all” |
webhook-enableSignatureValidation | boolean | false | Enable HMAC-SHA256 signature validation |
webhook-secretToken | string | - | Secret token for signature generation (auto-generated if empty) |
webhook-retryAttempts | number | 3 | Number of retry attempts (1-10) |
webhook-timeoutMs | number | 30000 | Request timeout in milliseconds (1000-120000) |
webhook-requireHttps | boolean | true | Require HTTPS URLs (security best practice) |
webhook-customHeaders | string | - | JSON object of custom headers to send |
webhook-userAgent | string | ”Royalti-Webhooks/1.0” | Custom User-Agent header |
Example: Complete Webhook Configuration
Webhook Monitoring & Analytics
Track your webhook performance using the Webhook Deliveries API:Get Delivery History
Get Delivery Summary
Get Detailed Analytics
Retry Failed Delivery
Security Best Practices
HMAC Signature Verification
When signature validation is enabled, Royalti signs all webhook payloads using HMAC-SHA256.Headers Sent by Royalti
Verification Implementation
Security Warning: Always use timing-safe comparison functions (
crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python, hash_equals in PHP) to prevent timing attacks.URL Validation
Royalti enforces strict URL validation for webhook endpoints: ✅ Allowed:- HTTPS URLs (required by default)
- Ports: 80, 443, 8080, 8443
- Public IP addresses and domains
-
HTTP URLs (unless
webhook-requireHttpsis false) - Private network ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Localhost (127.0.0.0/8)
- URLs longer than 2048 characters
Rate Limiting
Webhooks are subject to per-tenant rate limits:- Default: 100 deliveries per minute per webhook
- Auto-disable: Webhooks are automatically disabled after 50 consecutive failures
- Re-enable: Contact support or manually re-enable via settings
Error Handling & Retry Logic
HTTP Status Codes
Your webhook endpoint should return appropriate HTTP status codes:| Status Code | Meaning | Royalti’s Action |
|---|---|---|
| 200-299 | Success | Marks delivery as successful, no retry |
| 408, 504 | Timeout | Marks as timeout, retries with exponential backoff |
| 400-499 (except 408) | Client error | Marks as failed, logs error, no retry |
| 500-599 (except 504) | Server error | Marks as failed, retries with exponential backoff |
Retry Strategy
Royalti automatically retries failed webhook deliveries:- Initial Attempt: Delivered immediately when event occurs
- Retry 1: After 2 seconds (if initial failed)
- Retry 2: After 4 seconds (exponential backoff)
- Retry 3: After 8 seconds (exponential backoff)
- Final: Marked as failed after max attempts exhausted
-
webhook-retryAttempts: Set max retry attempts (default: 3, max: 10) - Exponential backoff with 2-second initial delay
- Each retry doubles the delay
Error Categories
Royalti categorizes webhook errors for easier debugging:| Error Code | Description | Common Causes |
|---|---|---|
TIMEOUT | Request timed out | Endpoint too slow, network issues |
NETWORK_ERROR | Connection failed | DNS failure, connection refused, socket errors |
CLIENT_ERROR | 4xx status code | Invalid request, authentication failure |
SERVER_ERROR | 5xx status code | Internal server error on your endpoint |
SSL_ERROR | SSL/TLS failure | Invalid certificate, expired certificate |
INVALID_URL | URL validation failed | Private IP, missing HTTPS, invalid format |
UNKNOWN_ERROR | Unclassified error | Unexpected errors |
Manual Retry
You can manually retry failed deliveries via API:Manual retries create a new delivery attempt with a 5-second delay and 1 retry attempt. The original delivery is marked as
finalAttempt: false.Testing & Development
Local Testing with ngrok
To test webhooks locally, use ngrok to expose your local server:1
Start your local server
2
Start ngrok tunnel
https://abc123.ngrok.io)3
Configure webhook URL
4
Trigger test events
Create an asset, make a payment, or perform other actions in Royalti to trigger webhook events.
Test Payload Examples
Payment Completed Event
Asset Created Event
Inbound Webhooks (You Send to Royalti)
Inbound webhooks allow external systems to send status updates TO Royalti when events occur in your systems.Royalty File Status Update
Notify Royalti when royalty file processing completes in your external system. Endpoint:POST /webhook/royalty/file-update/webhook
Authentication: royalti-x-hash header with HMAC signature
Payload:
Download Status Update
Notify Royalti when a download file is ready. Endpoint:POST /webhook/download/status-update/webhook
Authentication: royalti-x-hash header with HMAC signature
Payload:
Third-Party Webhooks (System Integration)
Royalti integrates with third-party services that send webhooks to the platform.Stripe Billing Events
Royalti receives Stripe webhooks for subscription and billing events. Supported Events:- Subscription lifecycle (created, updated, deleted)
- Invoice events (payment succeeded, payment failed)
- Checkout session completed
- Customer created/updated
- Billing meter events
These webhooks are handled automatically by Royalti. You don’t need to configure anything - Stripe sends them directly to Royalti’s webhook endpoint.
Cloudflare Domain & SSL Status
For custom domains configured through Cloudflare SaaS, Royalti receives:- Custom hostname events (created, deleted)
- SSL validation events (completed, failed)
- SSL renewal events (renewed, renewal failed)
Complete Code Examples
Express.js Webhook Server
Idempotency Implementation
Troubleshooting
Common Issues
Webhooks Not Received
✅ Checklist:-
Is
webhook-isActiveset totrue? -
Is
webhook-urlcorrectly configured with HTTPS? -
Are you subscribed to the event type (
webhook-enabledEvents)? - Check webhook delivery logs for error details
- Verify your endpoint is publicly accessible
- Check firewall/security group rules
Signature Verification Failures
✅ Checklist:- Are you using the raw request body (not parsed JSON)?
- Is your secret token correct?
- Are you using the same algorithm (HMAC-SHA256)?
- Check for character encoding issues
- Verify timing-safe comparison is used
Timeout Errors
✅ Solutions:- Optimize your webhook handler (should respond in <5 seconds)
- Process events asynchronously (queue for background processing)
-
Increase
webhook-timeoutMsif legitimately needed - Return 200 immediately, then process in background
High Failure Rate
✅ Investigation Steps:- Check delivery logs for error codes
- Monitor your endpoint’s error logs
- Verify endpoint uptime/availability
- Check for rate limiting on your server
- Review error distribution by event type
Best Practices
✅ Production Deployment- Always enable signature validation in production
- Use HTTPS for webhook endpoints (enforced by default)
- Respond quickly (<5 seconds) to prevent timeouts
- Process async - queue events for background processing
- Implement idempotency to handle duplicate events safely
- Log everything - delivery ID, event type, processing time
- Monitor delivery success rates via analytics API
- Set up alerts for high failure rates (>5%)
- Test thoroughly using ngrok in development
- Handle all event types gracefully (even unknown ones)
- Never log webhook payloads containing sensitive data
- Validate all webhook data before processing
- Use environment variables for secrets
- Implement rate limiting on your webhook endpoint
- Monitor for abnormal patterns (sudden spikes, unusual sources)
- Rotate secrets regularly (quarterly recommended)
- Use timing-safe comparison for signature verification
- Sanitize data before database insertion
- Implement IP whitelisting if possible
- Keep dependencies updated for security patches
Support & Resources
API Documentation:- API Reference - Complete API documentation for webhook endpoints
- Webhook Deliveries API - Monitor and manage webhook deliveries
- Support Portal - Get help from our support team
- Status Page - Check system status and uptime
FAQ
Can I receive webhooks for events that occurred before I configured my webhook?
Can I receive webhooks for events that occurred before I configured my webhook?
No, webhooks are only sent for events that occur AFTER your webhook is configured and enabled. Historical events are not sent retroactively.
How long does Royalti retry failed webhook deliveries?
How long does Royalti retry failed webhook deliveries?
Royalti retries failed deliveries based on your
webhook-retryAttempts configuration (default: 3 attempts) with exponential backoff starting at 2 seconds. After exhausting all retries, the delivery is marked as failed and can be manually retried via API.Can I configure different webhook URLs for different event types?
Can I configure different webhook URLs for different event types?
Currently, you can only configure one webhook URL per tenant. All subscribed event types are sent to the same endpoint. You can filter events in your webhook handler based on the
event field.What happens if my webhook endpoint is down?
What happens if my webhook endpoint is down?
Failed deliveries are retried automatically with exponential backoff. After max retries are exhausted, deliveries are logged as failed and can be viewed in the webhook deliveries API. If 50+ consecutive failures occur, the webhook is automatically disabled to prevent resource waste.
How do I test webhooks in development?
How do I test webhooks in development?
Use ngrok or a similar tunneling service to expose your local development server to the internet. Configure your webhook URL to the ngrok HTTPS URL and trigger events in Royalti to receive webhooks locally.
Are webhooks sent in order?
Are webhooks sent in order?
Webhooks are generally sent in the order events occur, but delivery order is not guaranteed due to retries and network variations. Implement idempotency and use event timestamps to handle out-of-order delivery.
Can I disable webhooks temporarily?
Can I disable webhooks temporarily?
Yes, set
webhook-isActive to false via the Tenant Settings API. Events that occur while webhooks are disabled will NOT be queued - they are lost permanently.How do I rotate my webhook secret token?
How do I rotate my webhook secret token?
Update the
webhook-secretToken setting with a new secure random value via the Tenant Settings API. Update your webhook endpoint code with the new secret before rotating to prevent signature verification failures.Last Updated: January 2025 Version: 1.0