T
Trickl
Server-to-server · Bearer auth

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

1

Buyer clicks “Pay with Trickl” on your platform

2

Your backend calls Trickl: POST /external/goals/create

3

Trickl returns a paymentUrl

4

You redirect the buyer to paymentUrl (Trickl-hosted page)

5

Buyer signs up on Trickl, links their bank account

6

Round-ups from everyday spending accumulate automatically

7

Trickl collects funds in small $3 micro-payments as round-ups build up

8

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

  1. 1Create a Trickl provider account (or sign in if you already have one)
  2. 2Go to your Provider Dashboard → Developer → Platform API
  3. 3Click Generate Platform API Key — your key appears once, copy it immediately
API Key: trickl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Store 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?

Multi-vendor

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/register for each vendor
  • 3Use their providerLinkCode to create goals
You are the vendor

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 linkCode in Dashboard → Developer → Embed Widget
  • 3Use your linkCode directly 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.app

Base URL — Development

http://localhost:3000

3Endpoints

POST/api/v1/external/providers/register
Multi-vendor onlyidempotent

Register 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

FieldRequiredDescription
stripeConnectAccountIdCreator's existing Stripe Connect account ID
externalCreatorIdYour own ID for this creator — used to de-duplicate registrations
businessNameCreator's display name shown to buyers
emailCreator's email (must be unique across Trickl)
webhookUrlHTTPS URL where Trickl sends payment webhooks
logoUrlCreator's logo, shown on the Trickl /pay/save page
websiteUrlCreator'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
  }
}
Store providerLinkCode and webhookSecret per creator in your database.
POST/api/v1/external/goals/create

Create 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

FieldRequiredDescription
providerLinkCodeMulti-vendor: vendor's link code from register response. You are the vendor: your own link code from Dashboard → Developer → Embed Widget.
targetAmountAmount in cents (e.g. 2999 = $29.99). Minimum: 50
currencyMust be "usd". Only USD is supported — omit or pass "usd".
descriptionProduct name shown to the buyer on the Trickl page
imageUrlProduct image URL shown on the Trickl /pay/save page
metadataAny 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.
callbackUrlWhere to redirect buyer after they confirm the goal
cancelUrlWhere to redirect buyer if they cancel
depositAmountOptional 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.
depositRefundableWhether to refund the deposit if the buyer cancels the goal. Default: false.
frequencyBilling 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"
}
After payment: Trickl fires 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"
  }
}
Redirect the buyer to paymentUrl immediately after this call.
GET/api/v1/external/goals/:goalId

Poll 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

SAVING

Buyer is accumulating round-ups toward the goal. Funds are collected in $3 micro-payments as they build up.

COMPLETED

Goal fully funded via micro-payments — grant access now. Creator payout follows immediately (transferId null if payout failed).

CANCELLED

Buyer cancelled the goal

REFUNDED

Payment was reversed

POST/api/v1/external/goals/:goalId/cancel

Cancel 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
  }
}
If the goal had a refundable deposit, the deposit payment is automatically refunded via Stripe. For subscriptions, cancellation stops future billing cycles.

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.

POST/api/v1/goals/:goalId/ticket
FieldRequiredDescription
qrDataThe 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.
noteOptional entry note shown below the QR code, e.g. "Doors open at 7pm · Photo ID required".
Authentication: Pass your 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:

Where is my webhook secret?
  • Multi-vendor: returned in the providers/register response as webhookSecret — store it per vendor.
  • You are the vendor: found in Dashboard → Developer → Webhook Settings → Reveal.
HeaderDescription
X-Trickl-SignatureHMAC-SHA256 signature
X-Trickl-TimestampUnix timestamp (seconds) — reject if > 5 minutes old
X-Trickl-Webhook-IdUnique 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

AttemptDelay
1st retry1 second
2nd retry5 seconds
3rd retry15 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

StatusCodeMeaning
400Missing or invalid request fields
401Invalid or missing API key
404PROVIDER_NOT_FOUNDproviderLinkCode not found or belongs to a different platform
409DUPLICATE_PROVIDERA provider with this email or Stripe account already exists
410Goal is no longer active (already completed or cancelled)
500Trickl 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