Webhooks Documentation
Receive real-time notifications when payment events occur in your Trickl subscriptions.
1Quick Start
- 1Set up an endpoint on your server to receive POST requests
- 2Configure your webhook URL in the Trickl Dashboard under Settings
- 3Save your webhook secret securely - you'll use it to verify signatures
- 4Test your endpoint using the built-in test feature
2Webhook Events
| Event | Description |
|---|---|
payment.completed | A round-up payment was successfully processed |
payment.failed | A payment attempt failed |
subscription.created | A new user subscribed to your service |
subscription.cancelled | A user cancelled their subscription |
subscription.paused | A subscription was paused |
| Goal & Platform API Events | |
goal.completed | A one-time savings goal was fully funded and paid out |
goal.deposit_paid | A buyer paid the upfront deposit on a goal |
goal.subscription_created | A buyer confirmed a recurring subscription via micro-payments |
goal.cycle_paid | A billing cycle was completed and paid out for a recurring subscription |
goal.cancelled | A savings goal or subscription was cancelled |
3Payload Structure
All webhook payloads follow this structure:
{
"id": "whevt_a1b2c3d4e5f6...",
"type": "payment.completed",
"timestamp": 1706745600,
"data": {
"subscriptionId": "sub_abc123",
"userId": "user_xyz789",
"userEmail": "customer@example.com",
"userName": "John Doe",
"providerId": "prov_123abc",
"tierName": "Premium",
"amount": 2.50,
"totalPaidThisCycle": 7.50,
"subscriptionAmount": 9.99,
"paymentProgress": 75,
"nextPaymentDue": "2024-02-15T00:00:00.000Z",
"paymentIntentId": "pi_...",
"transferId": "tr_..."
}
}3bGoal & Subscription Payloads
Goal events use a different payload structure. These are sent when using the Platform API to create goals and subscriptions.
goal.subscription_created
{
"id": "whevt_e5f6g7h8...",
"type": "goal.subscription_created",
"timestamp": 1709500800,
"data": {
"goalId": "goal_abc123",
"userId": "trickl_user_id",
"userEmail": "buyer@example.com",
"userName": "Jane Doe",
"providerId": "prov_abc123",
"frequency": "MONTHLY",
"nextBillingDate": "2026-04-03T00:00:00.000Z",
"metadata": { "planId": "plan_premium" }
}
}
// Fired when a buyer confirms a recurring subscription.
// Grant access to your content/service.goal.cycle_paid
{
"id": "whevt_i9j0k1l2...",
"type": "goal.cycle_paid",
"timestamp": 1709500800,
"data": {
"goalId": "goal_abc123",
"userId": "trickl_user_id",
"userEmail": "buyer@example.com",
"userName": "Jane Doe",
"providerId": "prov_abc123",
"amount": 9.99,
"transferId": "tr_...",
"frequency": "MONTHLY",
"cycleNumber": 2,
"nextBillingDate": "2026-05-03T00:00:00.000Z",
"metadata": { "planId": "plan_premium" }
}
}
// Fired each billing cycle. Renew the buyer's access.
// savedAmount resets to 0 — round-ups resume automatically.goal.completed
{
"id": "whevt_a1b2c3d4...",
"type": "goal.completed",
"timestamp": 1709500800,
"data": {
"goalId": "goal_xyz789",
"userId": "trickl_user_id",
"userEmail": "buyer@example.com",
"userName": "Jane Doe",
"providerId": "prov_abc123",
"amount": 20.00,
"depositAmount": 9.99,
"transferId": "tr_...",
"metadata": { "orderId": "order_123" }
}
}
// Fired when a one-time goal is fully funded.
// Grant product access to the buyer.goal.cancelled
{
"id": "whevt_m3n4o5p6...",
"type": "goal.cancelled",
"timestamp": 1709500800,
"data": {
"goalId": "goal_xyz789",
"userId": "trickl_user_id",
"userEmail": "buyer@example.com",
"userName": "Jane Doe",
"providerId": "prov_abc123",
"amount": 29.99,
"depositRefunded": true,
"metadata": { "orderId": "order_123" }
}
}
// Fired for both goal and subscription cancellations.
// Revoke access on your platform.4Security
Security Headers
Every webhook request includes these headers for verification:
| Header | Description |
|---|---|
X-Trickl-Signature | HMAC-SHA256 signature of the payload |
X-Trickl-Timestamp | Unix timestamp when webhook was sent |
X-Trickl-Webhook-Id | Unique ID for this webhook delivery |
Important: Always Verify Signatures
Never process a webhook without verifying its signature. This protects you from spoofed requests.
5Code Examples
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body for signature verification
app.use('/webhooks/trickl', express.raw({ type: 'application/json' }));
const WEBHOOK_SECRET = process.env.TRICKL_WEBHOOK_SECRET;
function verifySignature(payload, timestamp, signature) {
const signaturePayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(signaturePayload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
function isTimestampValid(timestamp) {
const now = Math.floor(Date.now() / 1000);
return Math.abs(now - timestamp) <= 300; // 5 minute tolerance
}
app.post('/webhooks/trickl', (req, res) => {
const signature = req.headers['x-trickl-signature'];
const timestamp = parseInt(req.headers['x-trickl-timestamp']);
const payload = req.body.toString();
// Verify timestamp (replay protection)
if (!isTimestampValid(timestamp)) {
return res.status(400).json({ error: 'Timestamp expired' });
}
// Verify signature
if (!verifySignature(payload, timestamp, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Parse and process the event
const event = JSON.parse(payload);
console.log(`Received ${event.type} webhook`);
switch (event.type) {
case 'payment.completed':
// Handle payment completed
break;
case 'subscription.created':
// Handle new subscription
break;
case 'subscription.cancelled':
// Handle cancellation
break;
case 'goal.completed':
// One-time goal funded — grant product access
break;
case 'goal.subscription_created':
// Recurring subscription confirmed — grant access
break;
case 'goal.cycle_paid':
// Billing cycle paid — renew access
break;
case 'goal.cancelled':
// Goal or subscription cancelled — revoke access
break;
}
res.status(200).json({ received: true });
});
app.listen(3000);6Best Practices
Respond Quickly
Return a 200 response immediately. Process webhooks asynchronously using a job queue.
Handle Idempotency
Use the webhook ID to prevent processing the same event twice. Store processed IDs in your database.
Handle Retries
Trickl retries failed webhooks 3 times with exponential backoff (1s, 5s, 15s).
Use HTTPS
In production, webhook URLs must use HTTPS to protect payload data.
7Testing Webhooks
Using the Dashboard
- 1. Go to Settings → Webhooks in your dashboard
- 2. Enter your endpoint URL
- 3. Click "Test Webhook"
- 4. Check your server logs for the test event
Local Development
Use a tunnel service like ngrok for local testing:
# Start ngrok
ngrok http 3000
# Use the ngrok URL in your Trickl dashboard
# https://abc123.ngrok.io/webhooks/tricklNeed Help?
Our support team is here to help you set up webhooks.
Contact Support