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 |
goal.deadline_charge_failed | A time-boxed goal's deadline passed while still short and the final charge could not be collected — withhold access |
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" }
// For a deadline goal completed by the automatic final charge, this event
// also includes: "deadlineCharged": true, "deadlineChargeSource": "card",
// "deadlineChargeAmount": 18.00
}
}
// Fired when a one-time goal is fully funded (including by a deadline top-up).
// 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.goal.deadline_charge_failed
{
"id": "whevt_q7r8s9t0...",
"type": "goal.deadline_charge_failed",
"timestamp": 1750014000,
"data": {
"goalId": "goal_xyz789",
"userId": "trickl_user_id",
"userEmail": "buyer@example.com",
"userName": "Jane Doe",
"providerId": "prov_abc123",
"amount": 50.00, // full target the buyer did not reach
"amountCollected": 32.00, // partial collected + paid out to the creator
"targetAmount": 50.00,
"deadline": "2026-06-15T19:00:00Z",
"metadata": { "ticketId": "tkt_abc123" }
}
}
// Fired when a time-boxed goal (created with a deadline) reaches its date
// while still short and the final charge can't be collected. The goal is
// marked EXPIRED and any partial is paid out to the creator; the buyer is
// NOT refunded. Withhold or 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;
case 'goal.deadline_charge_failed':
// Deadline passed while still short — withhold 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 5 times with exponential backoff (1m, 5m, 30m, 2h, 12h). Retries fire on 5xx, 408, 429 and network errors; any other 4xx is treated as permanent and not retried. Return 2xx once you have durably accepted the event.
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