Documentation
First Steps
Paddle Webhooks
Webhooks allow Paddle to notify your app of events such as completed payments, subscription changes, or refunds. Testing these webhooks, however, can be tricky without the right setup.
In this guide, you’ll learn webhook best practices, how to test Paddle webhooks locally, and ensure they’re securely verified. By the end, you’ll have a webhook handling system that’s ready for production.
Challenges when testing Paddle webhooks
Webhooks enable your app to react to events in Paddle’s system. Paddle sends these events to a specified URL, and your app processes them.
However, handling webhooks presents challenges:
- Difficult testing, eg. localhost isn’t accessible from the internet
- You need to validate event signatures to ensure they’re from Paddle
- Simulating real-world events like subscription upgrades or cancellations can be cumbersome
To overcome these challenges, you need a secure webhook handler, a way to simulate events, and a way to test events locally.
With the right tools and techniques, these challenges are manageable.
Step 1: Create a webhook handler
First things first: you need a webhook handler to process incoming events. This varies depending on your tech stack, but the general idea is the same.
Below is an example of how you can set up a webhook handler in Django:
# views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import hashlib
import hmac
import time
@csrf_exempt
def paddle_webhook(request):
secret = "your-paddle-webhook-secret" # Replace with your Paddle webhook secret
payload = request.body.decode('utf-8')
signature_header = request.headers.get('Paddle-Signature')
# Extract timestamp and signature from the Paddle-Signature header
try:
ts, signature = [
part.split("=")[1]
for part in signature_header.split(";")
]
ts = int(ts)
except (ValueError, IndexError):
return JsonResponse({'error': 'Invalid signature header'}, status=400)
# Optionally, check the timestamp to prevent replay attacks
current_time = int(time.time())
if abs(current_time - ts) > 300: # 5-minute tolerance
return JsonResponse({'error': 'Timestamp too old'}, status=400)
# Verify the signature
signed_payload = f"{ts}:{payload}"
expected_signature = hmac.new(
secret.encode('utf-8'),
signed_payload.encode('utf-8'),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected_signature, signature):
return JsonResponse({'error': 'Invalid signature'}, status=400)
# Process the event
data = request.POST # Paddle sends form-encoded data
event_type = data.get('alert_name')
if event_type == 'subscription_created':
subscription_id = data.get('subscription_id')
print(f"Subscription {subscription_id} created.")
elif event_type == 'payment_succeeded':
order_id = data.get('order_id')
print(f"Payment for order {order_id} succeeded.")
else:
print(f"Unhandled event type: {event_type}")
return JsonResponse({'status': 'success'})
In this implementation:
- The
secret
is your Paddle webhook secret, available in the Paddle dashboard. - The
Paddle-Signature
header contains the timestamp and HMAC signature. - Timestamps are checked to prevent replay attacks, and signatures are validated using HMAC-SHA256.
You'll also need to add a URL pattern to your Django app's urls.py
:
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('webhooks', views.paddle_webhook),
# ...your other URLs
]
Step 2: Simulate Paddle events locally
Paddle doesn’t provide a way to simulate events in a local development environment. To test your webhook handler, you need to simulate these events manually.
For this, you can use the usewebhook-cli to simulate and forward Paddle webhooks to your local server.
Installation
You can install it with this one-liner:
curl -sSL https://usewebhook.com/install.sh | bash
Then, use the usewebhook
command to start inspecting and forwarding webhook events:
$ usewebhook
> Dashboard: https://usewebhook.com/?id=123
> Webhook URL: https://usewebhook.com/123
You’ll get two URLs:
- Dashboard URL: View and manage incoming requests.
- Webhook URL: Set this as your endpoint in Paddle.
The dashboard shows incoming requests in real-time, including the payload, headers, and request ID.
You can even share your unique dashboard URL with your team to collaborate on debugging webhook issues.
Forwarding webhooks
You can now forward incoming requests to your local server for testing:
usewebhook --forward-to http://localhost:8000/webhooks
Replaying webhooks
Or replay a specific webhook from history:
usewebhook --request-id <request-ID> --forward-to http://localhost:8000/webhooks
This saves time when debugging issues by eliminating the need to re-trigger events manually. You can also inspect the payload and headers of incoming requests from your browser.
Step 3: Moving to production
Once your webhook endpoint is thoroughly tested locally, it’s time to deploy it to production. Keep the following considerations in mind:
- Validate Webhook Signatures: Always validate the webhook signature using Paddle’s library or your own implementation. This ensures that events genuinely come from Paddle and haven’t been tampered with.
- Use HTTPS: Paddle requires webhook URLs to use HTTPS. This secures communication between Paddle and your app, protecting sensitive data in transit.
- Use a Live Domain for Webhook URL: Update your webhook configuration in the Paddle dashboard to use your live domain. Avoid hardcoding the URL in your app to make it easier to switch between environments.
- Use Environment-Specific API Keys: Separate your test and live environments in Paddle. Use different API keys and webhook signing secrets for each environment to prevent accidental data contamination.
- Set Up Logging and Monitoring: Set up logging and monitoring for webhook events. This helps you track the health of your integration and quickly identify issues.
- Handle Retries and Duplicates: Paddle retries webhook events if your server doesn’t respond with a 2xx status code. Ensure your webhook handler is idempotent to prevent duplicate processing of events.
Wrapping up
Testing Paddle webhooks doesn’t need to be complicated. By implementing a secure handler, validating signatures, and using tools like usewebhook-cli, you can efficiently test and debug your webhooks in a local environment.
With these practices in place, your webhook handling system will be robust and production-ready.