← Back to Dashboard

Developer Guide

Integrate Ninja Backer tips into your applications, overlays, and automation tools.

Webhook Integration

Configure a webhook URL in your dashboard to receive HTTP POST requests when tips arrive.

Webhook Payload

{
  "type": "tip",
  "amount": 5.00,
  "currency": "USD",
  "name": "TipperName",
  "message": "Great stream!",
  "timestamp": 1702138446000
}

Payload Fields

FieldTypeDescription
typestringAlways "tip"
amountnumberAmount in the stated currency: 5.00 USD or 500 JPY (not minor units)
currencystringCurrency code (USD, EUR, GBP, etc.)
namestringTipper's display name
messagestringOptional message from tipper
timestampnumberNotification time in milliseconds; unchanged on retry
isTestbooleanPresent and true for simulated tips; never fulfill paid rewards for these
callbackIdstringOptional caller-supplied correlation value; not proof of payment
anonymousbooleanOptional anonymity preference; respect it when displaying the tip

Delivery failures retry automatically for up to seven days. Deduplicate the X-NinjaBacker-Delivery header: retries retain the same ID and payload. Destinations must be public HTTPS URLs without embedded credentials. Private/loopback addresses and private DNS answers are blocked. Up to three HTTPS redirects are allowed, with every destination checked. Each attempt has a five-second total deadline. Return a 2xx response after durably accepting the notification. Retry delays start at one minute and cap at one hour. Changing your endpoint stops retries to the previous URL.

Webhook Signature (Optional)

For added security, you can enable HMAC-SHA256 signatures on outgoing webhooks. When enabled, each webhook includes a signature header:

X-NinjaBacker-Signature: t=1703721600,v1=abc123def456...

Generate a Webhook Secret

Go to your Dashboard, expand the Webhook section, and click Generate to create a webhook secret.

Store this secret securely — you'll need it to verify incoming webhooks.

Verifying Signatures (Node.js)

Verify the exact raw request bytes before parsing JSON. Once signatures are enabled, reject missing or malformed signatures too. Check the header timestamp, not the payload timestamp: delayed retries retain their original payload and get a fresh signature. The example requires Express and your own durable persistTipOnce implementation; it is not a complete receiver.

const crypto = require('node:crypto');

function verifyWebhook(rawBody, signatureHeader, secret) {
  if (!Buffer.isBuffer(rawBody) || typeof signatureHeader !== 'string' || !secret) return false;
  const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(signatureHeader);
  if (!match) return false;
  const timestamp = Number(match[1]);
  if (!Number.isSafeInteger(timestamp) || Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300) return false;
  const expected = crypto.createHmac('sha256', secret)
    .update(match[1] + '.').update(rawBody).digest();
  return crypto.timingSafeEqual(expected, Buffer.from(match[2], 'hex'));
}

// Express example: mount before any express.json() middleware.
const express = require('express');
const app = express();
const secret = process.env.NINJABACKER_WEBHOOK_SECRET;
if (!secret) throw new Error('Configure NINJABACKER_WEBHOOK_SECRET');

app.post('/webhook', express.raw({ type: 'application/json', limit: '32kb' }), async (req, res) => {
  if (!verifyWebhook(req.body, req.headers['x-ninjabacker-signature'], secret)) {
    return res.status(401).send('Invalid signature');
  }
  let tip;
  try { tip = JSON.parse(req.body.toString('utf8')); }
  catch { return res.status(400).send('Invalid JSON'); }
  if (tip.type !== 'tip' || tip.isTest === true) return res.sendStatus(204);
  const deliveryId = req.headers['x-ninjabacker-delivery'];
  if (typeof deliveryId !== 'string' || !deliveryId) return res.sendStatus(400);
  try {
    // Implement this with your database/queue: atomically record deliveryId
    // under a unique constraint and enqueue the tip, ignoring duplicates.
    await persistTipOnce(deliveryId, tip);
    return res.sendStatus(204);
  } catch {
    return res.sendStatus(503); // NinjaBacker will retry; do not acknowledge lost work.
  }
});

Server-Sent Events (SSE)

For real-time integrations, connect using the private Tip ID from your dashboard, not your public username. Keep this token and overlay URLs out of public viewer links. EventSource reconnects automatically, but SSE has no replay or Last-Event-ID recovery. Use webhooks for durable automation; the first SSE message has type: "connected".

Endpoint

GET https://ninjabacker.com/v1/subscribe/{YOUR_TIP_ID}

JavaScript Example

const tipId = 'your_tip_id_here';
const events = new EventSource(`https://ninjabacker.com/v1/subscribe/${tipId}`);

events.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'tip') {
    if (data.isTest) return; // Ignore simulated tips for paid automation.
    console.log(data.currency + ' ' + data.amount + ' from ' + data.fromLabel);
    console.log(`Message: ${data.message}`);
  }
};

events.onerror = () => {
  console.log('Connection lost, reconnecting...');
};

SSE Event Format

{
  "type": "tip",
  "amount": 5.00,
  "currency": "USD",
  "fromLabel": "TipperName",
  "message": "Great stream!",
  "timestamp": 1702138446000
}

VDO.Ninja Integration

Add the &tip=YOUR_TIP_ID parameter to your VDO.Ninja URL to enable in-stream tipping.

https://vdo.ninja/?push=streamid&tip=YOUR_TIP_ID

URL Parameters

ParameterDescriptionExample
&tip=IDYour Tip ID from dashboard&tip=aS_WnIp8IPrV
&tipShow setup modal (no ID)&tip
&tipqrsize=NQR code size in pixels&tipqrsize=200
¬ipqrHide the viewer QR overlay¬ipqr
&tipamounts=A,B,CInitial preset amounts; performer metadata takes precedence&tipamounts=5,10,25,50
&tipcurrency=XXXInitial display currency; the performer controls charge currency&tipcurrency=EUR

For Viewers

Viewers must opt-in to see tip UI. Add &showtips to viewer URLs:

https://vdo.ninja/?view=streamid&showtips

Hiding the QR Code

If you prefer not to show the QR code overlay on your video:

https://vdo.ninja/?view=streamid&showtips¬ipqr

Custom QR Code Size

Adjust the QR code size (default is 150px):

https://vdo.ninja/?push=streamid&tip=YOUR_TIP_ID&tipqrsize=100

Payment API and Third-Party Checkout

For most integrations, link to https://ninjabacker.com/USERNAME?callback=YOUR_REFERENCE. The reference returns as callbackId; choose an opaque identifier, never a password or session token.

Custom checkouts can read GET /v1/public/performer/USERNAME, then create a payment using POST /v1/tip/intent with performerUsername, amount (major units), and currency. Initialize Stripe.js with the returned stripePublishableKey and stripeAccountId as its connected-account context. Compare the intent's amount and currency with the amount the viewer approved before confirming its client secret.

After Stripe succeeds, call POST /v1/tip/confirm with tipId and paymentIntentId. If notification confirmation fails, retain those IDs and retry that request; never create another payment for the same successful charge. A currency mismatch returns HTTP 409; show the new currency and require another explicit Send Tip action.

Supported currencies: USD, EUR, GBP, CAD, AUD, JPY. Bounds are 1?1000 major units for currencies with cents and 50?1000 JPY. GET /v1/tips/TIP_ID history uses minor units in amount; use display_amount for display. Webhooks and SSE already use major units. The legacy performer endpoint no longer exposes private Tip IDs by default.

Social Stream Integration

To use with Social Stream, enter your Social Stream webhook URL in the dashboard. Tips will appear alongside your chat messages.

Use your service's actual public HTTPS webhook URL, not its dashboard, browser-source URL, or SSE URL. For Streamer.bot or another desktop receiver, place an authenticated public HTTPS receiver in front of the local automation. Handle isTest separately and deduplicate retries before issuing gifts, credits, or other paid actions.

Testing

Use the "Send Test Tip" button in your dashboard to trigger a test notification without making a real payment. Test tips include "isTest": true in the payload.

Profile Avatar

Your profile avatar is automatically loaded from Gravatar using your Stripe account email.

Setting Up Your Avatar

  1. Go to gravatar.com and create an account
  2. Use the same email address you used when registering with Stripe
  3. Upload your profile picture
  4. Your avatar will automatically appear on your tip page

If no Gravatar is found, a default placeholder image is displayed.

← Back to Dashboard