Platform API
Integrate Trickl's round-up savings into your platform. Let your buyers pay for products through spare-change round-ups from everyday spending.
How it works
Buyer clicks “Pay with Trickl” on your platform
Your backend calls Trickl: POST /external/goals/create
Trickl returns a paymentUrl
You redirect the buyer to paymentUrl (Trickl-hosted page)
Buyer signs up on Trickl, links their bank account
Round-ups from everyday spending accumulate automatically
Trickl collects funds in small $3 micro-payments as round-ups build up
Target reached → Trickl fires a webhook (goal.completed or goal.cycle_paid for subscriptions)
You grant the buyer access — subscriptions auto-renew each cycle
As round-ups accumulate, Trickl collects funds from the buyer's bank in small $3 micro-payments — so progress is real and incremental. For subscriptions, this repeats each billing cycle automatically.
1Getting started
- 1Create a Trickl provider account (or sign in if you already have one)
- 2Go to your Provider Dashboard → Developer → Platform API
- 3Click Generate Platform API Key — your key appears once, copy it immediately
API Key: trickl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxStore this key securely on your server only. Never expose it to the browser or mobile clients. It is shown once and cannot be recovered — if lost, revoke and generate a new one from your dashboard.
Which mode are you?
You run a marketplace with multiple vendors. Your vendors have their own Stripe Connect accounts and you need to register each one with Trickl.
- 1Generate Platform API key from dashboard
- 2Call
providers/registerfor each vendor - 3Use their
providerLinkCodeto create goals
You are the vendor. You have your own Trickl provider account and want to accept savings goals from buyers directly on your own site or backend.
- 1Generate Platform API key from dashboard
- 2Skip vendor registration — find your own
linkCodein Dashboard → Developer → Embed Widget - 3Use your
linkCodedirectly when creating goals. Webhook secret is in Dashboard → Developer → Webhook Settings.
2Authentication
All platform API calls use Bearer token auth:
Authorization: Bearer trickl_live_xxxxxxxx...Base URL — Production
https://api.trickl.appBase URL — Development
http://localhost:30003Endpoints
/api/v1/external/providers/registerRegister a creator as a Trickl provider using their existing Stripe Connect account. Safe to call multiple times — returns existing credentials if already registered.
Request body
| Field | Required | Description |
|---|---|---|
stripeConnectAccountId | ✓ | Creator's existing Stripe Connect account ID |
externalCreatorId | ✓ | Your own ID for this creator — used to de-duplicate registrations |
businessName | ✓ | Creator's display name shown to buyers |
email | ✓ | Creator's email (must be unique across Trickl) |
webhookUrl | ✓ | HTTPS URL where Trickl sends payment webhooks |
logoUrl | — | Creator's logo, shown on the Trickl /pay/save page |
websiteUrl | — | Creator's website |
Request
POST /api/v1/external/providers/register
Authorization: Bearer trickl_live_...
{
"stripeConnectAccountId": "acct_1ABC2defGHIJ3klm",
"externalCreatorId": "your_platform_creator_id",
"businessName": "Jane's Film Studio",
"email": "jane@janefilms.com",
"webhookUrl": "https://yourplatform.com/webhooks/trickl",
"logoUrl": "https://yourplatform.com/logos/jane.png"
}Response 201
{
"success": true,
"data": {
"providerId": "prov_abc123",
"providerLinkCode": "mlt-a1b2c3d4",
"webhookSecret": "whsec_abc123...",
"webhookUrl": "https://yourplatform.com/webhooks/trickl",
"alreadyExists": false
}
}providerLinkCode and webhookSecret per creator in your database./api/v1/external/goals/createCreate a savings goal or recurring subscription for a buyer. Call this when the buyer selects the Trickl payment option on a product page. Redirect the buyer to the returned paymentUrl.
Request body
| Field | Required | Description |
|---|---|---|
providerLinkCode | ✓ | Multi-vendor: vendor's link code from register response. You are the vendor: your own link code from Dashboard → Developer → Embed Widget. |
targetAmount | ✓ | Amount in cents (e.g. 2999 = $29.99). Minimum: 50 |
currency | — | Must be "usd". Only USD is supported — omit or pass "usd". |
description | ✓ | Product name shown to the buyer on the Trickl page |
imageUrl | — | Product image URL shown on the Trickl /pay/save page |
metadata | — | Any JSON object — returned verbatim in the goal.completed webhook. For event tickets, include: eventDate (ISO 8601), eventVenue (string), seatInfo (string, e.g. "Section A · Row 3 · Seat 12"), tierName (string, e.g. "VIP Ticket"). These fields power the native ticket card shown in the Trickl mobile app after payment. |
callbackUrl | — | Where to redirect buyer after they confirm the goal |
cancelUrl | — | Where to redirect buyer if they cancel |
depositAmount | — | Optional upfront deposit in cents charged to the buyer's card at confirmation. For one-time goals: must be ≥ 50 and < targetAmount. For recurring subscriptions: must equal targetAmount (pay first month) or be omitted (free first month). Default: 0. |
depositRefundable | — | Whether to refund the deposit if the buyer cancels the goal. Default: false. |
frequency | — | Billing frequency for recurring subscriptions: "WEEKLY", "BIWEEKLY", "MONTHLY", "QUARTERLY", or "YEARLY". Omit for one-time products. Can be combined with depositAmount (= targetAmount) to charge the first month upfront. |
One-time product
POST /api/v1/external/goals/create
Authorization: Bearer trickl_live_...
{
"providerLinkCode": "mlt-a1b2c3d4",
"targetAmount": 2999,
"currency": "usd",
"description": "Advanced Filmmaking Course",
"imageUrl": "https://yourplatform.com/images/filmcourse.jpg",
"metadata": {
"productId": "prod_filmcourse_001",
"buyerId": "your_platform_user_456",
"platform": "your_platform_name"
},
"callbackUrl": "https://yourplatform.com/products/filmcourse?trickl=saving",
"cancelUrl": "https://yourplatform.com/products/filmcourse",
// Optional: require an upfront deposit (charged at buyer confirmation)
"depositAmount": 999, // $9.99 — must be ≥ 50 and < targetAmount
"depositRefundable": true // refund the deposit if buyer cancels
}Subscription
POST /api/v1/external/goals/create
Authorization: Bearer trickl_live_...
{
"providerLinkCode": "mlt-a1b2c3d4",
"targetAmount": 999,
"currency": "usd",
"description": "Premium Streaming Plan",
"imageUrl": "https://yourplatform.com/images/premium.jpg",
"metadata": {
"planId": "plan_premium",
"buyerId": "your_platform_user_456"
},
"callbackUrl": "https://yourplatform.com/plans/premium?trickl=subscribed",
"cancelUrl": "https://yourplatform.com/plans/premium",
// Recurring subscription — auto-renews each cycle via micro-payments
"frequency": "MONTHLY"
// Optional: charge the first month upfront (depositAmount must equal targetAmount)
// "depositAmount": 999 // Omit for free first month
}Event ticket
POST /api/v1/external/goals/create
Authorization: Bearer trickl_live_...
{
"providerLinkCode": "mlt-a1b2c3d4",
"targetAmount": 5000,
"currency": "usd",
"description": "Summer Music Festival 2026",
"imageUrl": "https://yourplatform.com/images/festival-banner.jpg",
// Ticket metadata — powers the native ticket card in the Trickl mobile app
"metadata": {
"eventDate": "2026-06-15T19:00:00Z", // ISO 8601 — shown as date + time pills
"eventVenue": "Madison Square Garden, New York", // shown as venue pill
"seatInfo": "Section A · Row 3 · Seat 12", // shown as seat pill
"tierName": "VIP Ticket", // shown as tier badge on the ticket
// Your internal IDs — returned verbatim in goal.completed webhook
"ticketId": "tkt_abc123",
"buyerId": "your_platform_user_456"
},
"callbackUrl": "https://yourplatform.com/tickets/confirm",
"cancelUrl": "https://yourplatform.com/events/summer-fest"
}goal.completed to your webhook. Call POST /goals/:goalId/ticket with the booking QR to unlock the native ticket card in the user's Trickl app. See Step 3 — Submit Ticket below.Response 201
// One-time product response
{
"success": true,
"data": {
"goalId": "goal_xyz789",
"paymentUrl": "https://trickl.app/pay/save?goal=goal_xyz789",
"targetAmount": 2999,
"currency": "usd",
"description": "Advanced Filmmaking Course",
"providerName": "Jane's Film Studio",
"type": "one_time"
}
}
// Subscription response — includes frequency and billing date
{
"success": true,
"data": {
"goalId": "goal_abc123",
"paymentUrl": "https://trickl.app/pay/save?goal=goal_abc123",
"targetAmount": 999,
"currency": "usd",
"description": "Premium Streaming Plan",
"providerName": "Jane's Film Studio",
"type": "subscription",
"frequency": "MONTHLY",
"nextBillingDate": "2026-04-03T00:00:00.000Z"
}
}paymentUrl immediately after this call./api/v1/external/goals/:goalIdPoll a goal or subscription's current status. Use this to check progress without relying solely on webhooks.
One-time goal
{
"success": true,
"data": {
"goalId": "goal_xyz789",
"type": "one_time",
"status": "SAVING",
"targetAmount": 2999,
"savedAmount": 1240,
"currency": "usd",
"progressPercent": 41,
"description": "Advanced Filmmaking Course",
"metadata": {
"productId": "prod_filmcourse_001",
"buyerId": "your_platform_user_456"
},
"providerName": "Jane's Film Studio",
"completedAt": null,
"createdAt": "2026-02-25T10:00:00.000Z",
"depositAmount": 999,
"depositPaid": true,
"depositRefundable": true
}
}Subscription
{
"success": true,
"data": {
"goalId": "goal_abc123",
"type": "subscription",
"status": "SAVING",
"targetAmount": 999,
"savedAmount": 320,
"currency": "usd",
"progressPercent": 32,
"description": "Premium Streaming Plan",
"frequency": "MONTHLY",
"currentCycleNumber": 2,
"nextBillingDate": "2026-04-03T00:00:00.000Z",
"cycleStartDate": "2026-03-03T00:00:00.000Z",
"metadata": {
"planId": "plan_premium",
"buyerId": "your_platform_user_456"
},
"providerName": "Jane's Film Studio",
"createdAt": "2026-02-03T10:00:00.000Z"
}
}Goal statuses
SAVINGBuyer is accumulating round-ups toward the goal. Funds are collected in $3 micro-payments as they build up.
COMPLETEDGoal fully funded via micro-payments — grant access now. Creator payout follows immediately (transferId null if payout failed).
CANCELLEDBuyer cancelled the goal
REFUNDEDPayment was reversed
/api/v1/external/goals/:goalId/cancelCancel a savings goal or subscription. Platform-initiated — uses your API key, not user auth. Fires a goal.cancelled webhook.
POST /api/v1/external/goals/goal_xyz789/cancel
Authorization: Bearer trickl_live_...
// Response
{
"success": true,
"data": {
"goalId": "goal_xyz789",
"status": "CANCELLED",
"depositRefunded": true // true if deposit was refundable and refund succeeded
}
}3Submit Ticket (event tickets only)
When a user finishes paying for an event ticket goal, Trickl fires a goal.completed webhook to your platform. After generating or retrieving the booking QR code, call this endpoint to push it to the user's Trickl app. The ticket card is locked and invisible until this call succeeds.
/api/v1/goals/:goalId/ticket| Field | Required | Description |
|---|---|---|
qrData | ✓ | The string the QR code should encode — a booking reference, URL, UUID, or any value your scanner reads. Trickl generates the QR natively in the app. |
note | — | Optional entry note shown below the QR code, e.g. "Doors open at 7pm · Photo ID required". |
webhookSecret as the Bearer token — the same secret you use to verify incoming Trickl webhooks.// Called inside your goal.completed webhook handler
POST /api/v1/goals/goal_xyz789/ticket
Authorization: Bearer whsec_abc123... // your webhookSecret, NOT your API key
Content-Type: application/json
{
"qrData": "TKT-2026-XJ88K", // booking reference your scanner reads
"note": "Doors open at 7pm · Photo ID required"
}
// Response 200
{ "success": true }Full webhook → ticket flow
app.post('/webhooks/trickl', express.raw({ type: 'application/json' }), async (req, res) => {
const event = JSON.parse(req.body.toString());
// ... verify signature, deduplicate (see Webhooks section) ...
if (event.type === 'goal.completed') {
const { ticketId, buyerId } = event.data.metadata;
const goalId = event.data.goalId;
// 1. Grant access on your side
await grantTicketAccess(ticketId, buyerId);
// 2. Generate or retrieve the QR booking reference
const bookingRef = await getBookingQrRef(ticketId);
// 3. Push ticket to Trickl — unlocks the ticket card in the user's app
await fetch(`https://api.trickl.com/api/v1/goals/${goalId}/ticket`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TRICKL_WEBHOOK_SECRET}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
qrData: bookingRef,
note: 'Doors open at 7pm · Photo ID required',
}),
});
}
res.status(200).send('ok');
});What qrData can be
- • A booking reference string
- • A check-in URL
- • A UUID or order ID
- • Any string your scanner reads
What the app renders
- • Event image from imageUrl
- • Date, venue, seat pills
- • Tear-line stub design
- • QR code from qrData
Security
- • Ticket only visible when COMPLETED
- • Auth via webhook secret
- • Can only be set once per goal
- • Goal must belong to your provider
4Webhooks
When round-ups are collected, a goal completes, or a subscription cycle is paid, Trickl POSTs to the webhook URL configured for that provider. Every delivery includes three headers:
- Multi-vendor: returned in the
providers/registerresponse aswebhookSecret— store it per vendor. - You are the vendor: found in Dashboard → Developer → Webhook Settings → Reveal.
| Header | Description |
|---|---|
X-Trickl-Signature | HMAC-SHA256 signature |
X-Trickl-Timestamp | Unix timestamp (seconds) — reject if > 5 minutes old |
X-Trickl-Webhook-Id | Unique event ID — use for idempotency |
goal.completed
{
"id": "whevt_abc123def456",
"type": "goal.completed",
"timestamp": 1740484800,
"data": {
"goalId": "goal_xyz789",
"userId": "trickl_internal_user_id",
"userEmail": "buyer@example.com",
"userName": "Alex Johnson",
"providerId": "prov_abc123",
"amount": 20.00, // Total goal amount charged (micro-payments + final remaining)
"depositAmount": 9.99, // Deposit charged at confirmation (0 if no deposit)
"transferId": "tr_stripe_transfer_id",
"metadata": {
"productId": "prod_filmcourse_001",
"buyerId": "your_platform_user_456",
"platform": "your_platform_name"
}
}
}goal.deposit_paid
Fired immediately when a buyer confirms a goal that has a depositAmount > 0. Use this to show buyers their spot is secured while round-ups accumulate. goal.completed fires later when the full target amount is reached.
{
"id": "whevt_...",
"type": "goal.deposit_paid",
"timestamp": 1740484800,
"data": {
"goalId": "goal_xyz789",
"userId": "trickl_internal_user_id",
"userEmail": "buyer@example.com",
"userName": "Alex Johnson",
"providerId": "prov_abc123",
"amount": 9.99, // Deposit charged (same as depositAmount)
"depositAmount": 9.99,
"metadata": {
"productId": "prod_filmcourse_001",
"buyerId": "your_platform_user_456"
}
}
}goal.round_up_collected
Fired each time a $3 micro-payment is collected via ACH from round-ups. Use this to build a payment timeline or show creators real-time progress. Fires for both one-time goals and recurring subscription goals. The amount is typically $3.00 but may be larger on the final collection if the smart-remainder logic absorbs a sub-$3 leftover.
{
"id": "whevt_...",
"type": "goal.round_up_collected",
"timestamp": 1740484800,
"data": {
"goalId": "goal_xyz789",
"userId": "trickl_internal_user_id",
"userEmail": "buyer@example.com",
"userName": "Alex Johnson",
"providerId": "prov_abc123",
"amount": 3.00, // Micro-payment just collected ($3.00 standard)
"savedAmount": 6.00, // Cumulative total saved after this payment
"targetAmount": 29.99, // Goal target amount
"paymentProgress": 20, // Percentage toward goal (0-100)
"metadata": {
"productId": "prod_filmcourse_001",
"buyerId": "your_platform_user_456"
}
}
}goal.subscription_created
Fired when a buyer confirms a recurring subscription. Grant the buyer access to your content/service.
{
"id": "whevt_...",
"type": "goal.subscription_created",
"timestamp": 1740484800,
"data": {
"goalId": "goal_abc123",
"userId": "trickl_internal_user_id",
"userEmail": "buyer@example.com",
"userName": "Alex Johnson",
"providerId": "prov_abc123",
"frequency": "MONTHLY",
"nextBillingDate": "2026-04-03T00:00:00.000Z",
"metadata": {
"planId": "plan_premium",
"buyerId": "your_platform_user_456"
}
}
}goal.cycle_paid
Fired each time a billing cycle completes. Renew the buyer's access for the next period. savedAmount resets to 0 and round-ups resume for the next cycle.
{
"id": "whevt_...",
"type": "goal.cycle_paid",
"timestamp": 1740484800,
"data": {
"goalId": "goal_abc123",
"userId": "trickl_internal_user_id",
"userEmail": "buyer@example.com",
"userName": "Alex Johnson",
"providerId": "prov_abc123",
"amount": 9.99,
"transferId": "tr_stripe_transfer_id",
"frequency": "MONTHLY",
"cycleNumber": 2,
"nextBillingDate": "2026-05-03T00:00:00.000Z",
"metadata": {
"planId": "plan_premium",
"buyerId": "your_platform_user_456"
}
}
}goal.cancelled
Also fired for subscription cancellations. Revoke access on your platform. depositRefunded is false when depositAmount was 0, depositRefundable was false, or the Stripe refund call failed (goal is still cancelled).
{
"id": "whevt_...",
"type": "goal.cancelled",
"timestamp": 1740484800,
"data": {
"goalId": "goal_xyz789",
"userId": "trickl_internal_user_id",
"userEmail": "buyer@example.com",
"userName": "Alex Johnson",
"providerId": "prov_abc123",
"amount": 29.99,
"depositAmount": 9.99, // 0 if no deposit was required
"depositRefundable": true, // Whether the goal was set to refund on cancel
"depositRefunded": true, // true only if Stripe refund actually succeeded
"metadata": {
"productId": "prod_filmcourse_001",
"buyerId": "your_platform_user_456",
"platform": "your_platform_name"
}
}
}goal.payment_failed
Fired when an ACH micro-payment fails after being initiated (e.g. insufficient funds, closed account). The savedAmount is reversed back to pending tracking. Update your UI to reflect the corrected progress.
{
"id": "whevt_...",
"type": "goal.payment_failed",
"timestamp": 1740484800,
"data": {
"goalId": "goal_xyz789",
"userId": "trickl_internal_user_id",
"userEmail": "buyer@example.com",
"userName": "Alex Johnson",
"providerId": "prov_abc123",
"amount": 3.00, // The failed charge amount (dollars)
"savedAmount": 9.00, // Updated savedAmount after reversal (dollars)
"targetAmount": 15.00, // Goal target (dollars)
"paymentProgress": 60, // Updated progress percentage after reversal
"metadata": {
"productId": "prod_filmcourse_001",
"buyerId": "your_platform_user_456",
"platform": "your_platform_name"
}
}
}Verification (Node.js)
import crypto from 'crypto';
import express from 'express';
function verifyTricklWebhook(
rawBody: string,
signature: string,
timestamp: string,
secret: string
): boolean {
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// IMPORTANT: must receive raw body
app.post('/webhooks/trickl', express.raw({ type: 'application/json' }), async (req, res) => {
const rawBody = req.body.toString();
const signature = req.headers['x-trickl-signature'] as string;
const timestamp = req.headers['x-trickl-timestamp'] as string;
const webhookId = req.headers['x-trickl-webhook-id'] as string;
const event = JSON.parse(rawBody);
const creator = await db.creators.findFirst({
where: { tricklProviderId: event.data.providerId }
});
if (!verifyTricklWebhook(rawBody, signature, timestamp, creator.tricklWebhookSecret)) {
return res.status(401).send('Invalid signature');
}
// Deduplicate
const alreadyProcessed = await db.processedWebhooks.findUnique({ where: { id: webhookId } });
if (alreadyProcessed) return res.status(200).send('ok');
if (event.type === 'goal.completed') {
const { productId, buyerId, ticketId } = event.data.metadata;
await grantProductAccess(productId, buyerId);
await db.processedWebhooks.create({ data: { id: webhookId } });
// For event tickets: push QR code to Trickl to unlock the ticket card in the app
if (ticketId) {
const qrRef = await getBookingQrRef(ticketId);
await fetch(`https://api.trickl.com/api/v1/goals/${event.data.goalId}/ticket`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${creator.tricklWebhookSecret}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ qrData: qrRef }),
});
}
}
res.status(200).send('ok');
});Retry policy
| Attempt | Delay |
|---|---|
| 1st retry | 1 second |
| 2nd retry | 5 seconds |
| 3rd retry | 15 seconds |
4xx responses (except 408 and 429) are not retried. Use the polling endpoint to check goal state.
5Suggested database fields
Add these columns to your existing creators table:
-- Additions to your creators table
ALTER TABLE creators ADD COLUMN trickl_enabled BOOLEAN DEFAULT FALSE;
ALTER TABLE creators ADD COLUMN trickl_provider_id VARCHAR(50);
ALTER TABLE creators ADD COLUMN trickl_link_code VARCHAR(50);
ALTER TABLE creators ADD COLUMN trickl_webhook_secret VARCHAR(100);
-- Idempotency table for processed webhooks
CREATE TABLE processed_trickl_webhooks (
id VARCHAR(100) PRIMARY KEY, -- X-Trickl-Webhook-Id value
created_at TIMESTAMP DEFAULT NOW()
);6Error reference
| Status | Code | Meaning |
|---|---|---|
400 | — | Missing or invalid request fields |
401 | — | Invalid or missing API key |
404 | PROVIDER_NOT_FOUND | providerLinkCode not found or belongs to a different platform |
409 | DUPLICATE_PROVIDER | A provider with this email or Stripe account already exists |
410 | — | Goal is no longer active (already completed or cancelled) |
500 | — | Trickl internal error — retry with backoff |
Ready to integrate?
Go to your dashboard to generate your Platform API key, or contact us if you need help.
Contact support