Skip to main content

Overview

Webhooks enable real-time event notifications between Royalti.io and your systems. This guide covers two distinct webhook systems:
  1. Outbound Webhooks - Royalti sends event notifications TO your endpoints when events occur
  2. 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, as a JSON array:
Event values are literal strings, not symbolic keys — see the Event Catalog below. enabledEvents and the payload’s event field are matched on this exact string. enabledEvents must be a JSON array (or the literal string "all") — a comma-separated string is silently accepted but never matches anything.
4

Implement Endpoint

Create an HTTP endpoint that accepts POST requests:

Event Catalog

Royalti.io emits 40 event types across the categories below.
Use the exact string in the “Event Value” column — not a symbolic key. This is what the payload’s event field contains, and what you must supply as elements of the webhook-config.enabledEvents JSON array (or the literal string "all"). These strings are the literal NotificationType enum values from src/interface/Enums.ts; the enum’s internal key names (e.g. PAYMENT_COMPLETED) never appear on the wire and are rejected if you configure enabledEvents with them.

Financial Events (Require Signature Validation)

These events involve sensitive financial data and automatically enforce HMAC signature validation:

Catalog Events (Default Enabled)

Roster Events (Default Enabled)

Royalty Events (Require Explicit Opt-in)

High-volume events that require explicit opt-in to prevent overwhelming webhooks:

Split Events (Require Signature Validation)

Release Lifecycle Events (Default Enabled)

Emitted after a release transitions status through the Release API (submit, approve/reject review — single or bulk, manual status revert) or via the release auto-creation queue worker on success. resource.type is release, resource.id is the release ID, resource.url is /api/v1/releases/{id}, and resource.displayName is the release title.

DDEX Provider Delivery Events (Default Enabled)

Emitted by the provider-agnostic DDEX delivery manager, so queue-driven deliveries fire these events as well as interactively-triggered ones. resource.type is providerDelivery and resource.id is the delivery ID (falls back to the message ID when no delivery ID has been assigned yet).

Webhook Payload Structure

All outbound webhooks follow this standardized structure:

Field Descriptions


Webhook Configuration

Configure your webhook behavior through Tenant Settings — PUT /tenant/settings/{name} (requires the admin role or higher).
Setting names require the config. segment. The delivery pipeline (webhookProcessor) only ever reads settings named webhook-config.<field> — e.g. webhook-config.url, webhook-config.enabledEvents. Flat names like webhook-url are accepted by this endpoint with no validation and no error, get stored, and are then silently never read by anything. If your webhook never fires despite a “successful” PUT response, this is the first thing to check.

Available Settings

Delivery health metrics (webhook-metrics.lastDeliveryAttempt, totalDeliveries, healthScore, etc.) are computed and written by the delivery pipeline itself — they’re read-only and appear in the webhook object of any settings-update response, not something you PUT.

Example: Complete Webhook Configuration


Webhook Monitoring & Analytics

Track your webhook performance using the Webhook Deliveries API:

Get Delivery History

Response:

Get Delivery Summary

Response:

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
⚠️ Blocked:
  • HTTP URLs (unless webhook-config.requireHttps is 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:

Retry Strategy

Royalti automatically retries failed webhook deliveries:
  1. Initial Attempt: Delivered immediately when event occurs
  2. Retry 1: After 2 seconds (if initial failed)
  3. Retry 2: After 4 seconds (exponential backoff)
  4. Retry 3: After 8 seconds (exponential backoff)
  5. Final: Marked as failed after max attempts exhausted
Configuration:
  • webhook-config.retryAttempts: Set max retry attempts (default: 3, range: 0–10)
  • Exponential backoff with 2-second initial delay
  • Each retry doubles the delay

Error Categories

Royalti categorizes webhook errors for easier debugging:

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

Copy the HTTPS URL provided (e.g., 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:
Example:

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:
Example:

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:
  1. Is webhook-config.isActive set to true?
  2. Is webhook-config.url correctly configured with HTTPS?
  3. Are you subscribed to the event type (webhook-config.enabledEvents, as a JSON array — not webhook-enabledEvents, which is silently never read)?
  4. Check webhook delivery logs for error details
  5. Verify your endpoint is publicly accessible
  6. Check firewall/security group rules

Signature Verification Failures

✅ Checklist:
  1. Are you using the raw request body (not parsed JSON)?
  2. Is your secret token correct?
  3. Are you using the same algorithm (HMAC-SHA256)?
  4. Check for character encoding issues
  5. Verify timing-safe comparison is used
Debug Example:

Timeout Errors

✅ Solutions:
  1. Optimize your webhook handler (should respond in <5 seconds)
  2. Process events asynchronously (queue for background processing)
  3. Increase webhook-config.timeoutMs if legitimately needed
  4. Return 200 immediately, then process in background
Background Processing Pattern:

High Failure Rate

✅ Investigation Steps:
  1. Check delivery logs for error codes
  2. Monitor your endpoint’s error logs
  3. Verify endpoint uptime/availability
  4. Check for rate limiting on your server
  5. Review error distribution by event type
Query Recent Failures:

Best Practices

✅ Production Deployment
  1. Always enable signature validation in production
  2. Use HTTPS for webhook endpoints (enforced by default)
  3. Respond quickly (<5 seconds) to prevent timeouts
  4. Process async - queue events for background processing
  5. Implement idempotency to handle duplicate events safely
  6. Log everything - delivery ID, event type, processing time
  7. Monitor delivery success rates via analytics API
  8. Set up alerts for high failure rates (>5%)
  9. Test thoroughly using ngrok in development
  10. Handle all event types gracefully (even unknown ones)
⚠️ Security Requirements
  1. Never log webhook payloads containing sensitive data
  2. Validate all webhook data before processing
  3. Use environment variables for secrets
  4. Implement rate limiting on your webhook endpoint
  5. Monitor for abnormal patterns (sudden spikes, unusual sources)
  6. Rotate secrets regularly (quarterly recommended)
  7. Use timing-safe comparison for signature verification
  8. Sanitize data before database insertion
  9. Implement IP whitelisting if possible
  10. Keep dependencies updated for security patches

Support & Resources

API Documentation: External Resources:

FAQ

No, webhooks are only sent for events that occur AFTER your webhook is configured and enabled. Historical events are not sent retroactively.
Royalti retries failed deliveries based on your webhook-config.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.
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.
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.
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.
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.
Yes, set webhook-config.isActive to false via the Tenant Settings API. Events that occur while webhooks are disabled will NOT be queued - they are lost permanently.
Update the webhook-config.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