Skip to main content
The Tip Stack SDK API gives React and Next.js applications full programmatic control over the tipping flow. Instead of dropping in a static HTML snippet, you call three lightweight REST endpoints — initialize a session, trigger a tip intent, and subscribe to confirmed-tip events — and build your own UI on top.
Never expose your API key on the client side. Make the /sdk/init call from your backend and return only the sessionToken to your frontend.

Integration steps

1

Get your API key

Open your creator dashboard and navigate to Settings → Developer → API Keys. Generate a new secret key and store it securely in your server-side environment variables (e.g. .env.local for Next.js).
.env.local
TIPSTACK_API_KEY=your_secret_api_key_here
2

Initialize an SDK session

Call POST /api/sdk/init from your server (an API route, a Server Component, or a Next.js Route Handler). Pass your API key in the Authorization header along with the creator’s ID and the origin URL of the embedding page.
app/api/tipstack/session/route.ts
// Next.js App Router — server-side only
export async function POST(request: Request) {
  const { creatorId } = await request.json();

  const res = await fetch('https://tipstack.fun/api/sdk/init', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.TIPSTACK_API_KEY}`,
    },
    body: JSON.stringify({
      creatorId,
      originUrl: process.env.NEXT_PUBLIC_SITE_URL, // e.g. https://myblog.com
      theme: 'dark',
    }),
  });

  const data = await res.json();
  // Return only the sessionToken — never forward your API key
  return Response.json({ sessionToken: data.sessionToken, embedUrl: data.config.embedUrl });
}
A successful response looks like:
{
  "success": true,
  "sessionToken": "sdk_sess_<uuid>",
  "config": {
    "creatorId": "<uuid>",
    "creatorAddress": "Ab3C...7xYz",
    "acceptedTokens": ["SOL", "USDC"],
    "embedUrl": "https://tipstack.fun/checkout/<uuid>?theme=dark"
  }
}
The sessionToken (prefixed sdk_sess_) authorizes all subsequent SDK calls for this page load. The embedUrl is a fully hosted checkout page you can render directly in an <iframe> — see Rendering the hosted checkout below.
3

Trigger a tip

Call POST /api/sdk/tip from the client using the sessionToken returned in the previous step. Supply the tip amount, the token mint address, the sender’s wallet address, and the creator’s ID.
const sendTip = async (sessionToken: string, creatorId: string, amount: number) => {
  const res = await fetch('https://tipstack.fun/api/sdk/tip', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${sessionToken}`,
    },
    body: JSON.stringify({
      creatorId,
      amount,                  // USD value, e.g. 5
      inputTokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC mint
      sourceWalletAddress: 'YOUR_WALLET_ADDRESS',
    }),
  });

  const data = await res.json();
  // { success: true, intentId: "pi_<uuid>", status: "requires_action" }
  return data.intentId;
};
The response returns an intentId and a status of requires_action, indicating the user must sign the transaction in their wallet to complete the tip.
4

Listen for confirmed tip events

Poll GET /api/sdk/events with your API key (server-side) to receive a list of confirmed tip events for a creator. Use the optional since query parameter to fetch only events after a given ISO timestamp.
app/api/tipstack/events/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const creatorId = searchParams.get('creatorId');
  const since = searchParams.get('since') ?? '';

  const url = new URL('https://tipstack.fun/api/sdk/events');
  url.searchParams.set('creatorId', creatorId ?? '');
  if (since) url.searchParams.set('since', since);

  const res = await fetch(url.toString(), {
    headers: {
      'Authorization': `Bearer ${process.env.TIPSTACK_API_KEY}`,
    },
  });

  return Response.json(await res.json());
}
Each event in the response array has this shape:
{
  "eventId": "<txSignature>",
  "type": "tip_completed",
  "amountUsdc": 5,
  "amountRaw": 5,
  "tokenSymbol": "USDC",
  "sender": "<walletAddress>",
  "timestamp": "2024-11-01T12:34:56.000Z",
  "txSignature": "<txSignature>"
}
Up to 50 confirmed tips are returned per request, ordered newest-first.

Full React example

The component below combines all three steps into a self-contained TipButton. The /sdk/init call is proxied through your own Next.js API route so the API key never reaches the browser.
TipButton.tsx
import { useState, useEffect } from 'react';

export function TipButton({ creatorId }: { creatorId: string }) {
  const [sessionToken, setSessionToken] = useState<string | null>(null);

  useEffect(() => {
    // Call your own server-side proxy — never call /sdk/init directly from the client
    fetch('/api/tipstack/session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ creatorId }),
    })
      .then(r => r.json())
      .then(data => setSessionToken(data.sessionToken));
  }, [creatorId]);

  const sendTip = async (amount: number) => {
    if (!sessionToken) return;
    const res = await fetch('https://tipstack.fun/api/sdk/tip', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${sessionToken}`,
      },
      body: JSON.stringify({
        creatorId,
        amount,
        inputTokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
        sourceWalletAddress: 'YOUR_WALLET_ADDRESS',
      }),
    });
    const data = await res.json();
    console.log('Tip intent:', data.intentId);
  };

  return (
    <button onClick={() => sendTip(5)} disabled={!sessionToken}>
      ⚡ Tip $5
    </button>
  );
}
The /sdk/init call in the example above goes through /api/tipstack/session — your own server-side route that holds the API key. Never call https://tipstack.fun/api/sdk/init directly from the browser; doing so would expose your secret API key to anyone who inspects network requests.

Rendering the hosted checkout

If you prefer a fully managed UI instead of building your own, render the embedUrl from /sdk/init inside an <iframe>. Tip Stack hosts and updates the checkout experience — you just provide the container.
EmbedCheckout.tsx
export function EmbedCheckout({ embedUrl }: { embedUrl: string }) {
  return (
    <iframe
      src={embedUrl}
      title="Tip Stack Checkout"
      width="100%"
      height="480"
      allow="clipboard-write"
      style={{ border: 'none', borderRadius: '16px' }}
    />
  );
}
The embedUrl includes your chosen theme as a query parameter (e.g. ?theme=dark). To switch themes, re-initialize the session with a different theme value.

Listening for tip events in the client

To update your UI in real time when a tip is confirmed, poll your own events proxy on a short interval and compare the latest timestamp.
useTipEvents.ts
import { useState, useEffect, useRef } from 'react';

interface TipEvent {
  eventId: string;
  type: string;
  amountUsdc: number;
  amountRaw: number;
  tokenSymbol: string;
  sender: string;
  timestamp: string;
  txSignature: string;
}

export function useTipEvents(creatorId: string, pollIntervalMs = 5000) {
  const [events, setEvents] = useState<TipEvent[]>([]);
  const sinceRef = useRef<string>(new Date().toISOString());

  useEffect(() => {
    const poll = async () => {
      const res = await fetch(
        `/api/tipstack/events?creatorId=${creatorId}&since=${sinceRef.current}`
      );
      const data = await res.json();

      if (data.events?.length) {
        setEvents(prev => [...data.events, ...prev]);
        // Advance the cursor to avoid re-fetching the same events
        sinceRef.current = data.events[0].timestamp;
      }
    };

    const interval = setInterval(poll, pollIntervalMs);
    return () => clearInterval(interval);
  }, [creatorId, pollIntervalMs]);

  return events;
}

API reference

Initializes a new SDK session and validates the embedding origin.Headers
HeaderValue
AuthorizationBearer YOUR_API_KEY
Content-Typeapplication/json
Body
FieldTypeRequiredDescription
creatorIdstringYesCreator handle, wallet address, or user ID.
originUrlstringYesFull origin of the embedding site, e.g. https://myblog.com.
themestringNodark (default) or light.
Response200 OK
FieldDescription
sessionTokenShort-lived token (sdk_sess_<uuid>) for subsequent calls.
config.creatorIdThe creator’s UUID.
config.creatorAddressThe creator’s wallet address, masked (e.g. Ab3C...7xYz).
config.acceptedTokensArray of accepted token symbols: ["SOL", "USDC"].
config.embedUrlHosted checkout URL ready to use in an <iframe>.
Creates a tip intent within the current SDK session.Headers
HeaderValue
AuthorizationBearer sdk_sess_<uuid> (session token from /sdk/init)
Content-Typeapplication/json
Body
FieldTypeRequiredDescription
creatorIdstringYesCreator to tip.
amountnumberYesUSD amount of the tip.
inputTokenMintstringYesSPL token mint address. Use EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v for USDC.
sourceWalletAddressstringYesSender’s Solana wallet address.
Response200 OK
FieldDescription
intentIdUnique tip intent ID (pi_<uuid>).
statusAlways requires_action — the user must sign in their wallet.
Returns confirmed tip events for a creator, newest-first.Headers
HeaderValue
AuthorizationBearer YOUR_API_KEY
Query parameters
ParameterRequiredDescription
creatorIdYesThe creator’s ID to fetch events for.
sinceNoISO 8601 timestamp. Only events after this time are returned.
Response200 OKReturns up to 50 tip_completed events. Each event includes eventId, type, amountUsdc, amountRaw, tokenSymbol, sender, timestamp, and txSignature.