FishriftDocs Dashboard

External donations

Send donations from your platform, like a charity or tipping site, into Fishrift. Each donation plays the creator's alerts, shows in their activity feed, counts towards goals and subathons, and can fire OBS Actions and Streamer.bot.

Getting access

Fishrift sets up each connection for you. Contact Fishrift with the creator's name and your platform's name. You get:

  • a source ID, used in the webhook URL
  • a shared secret, used to sign requests. It's shown once, so store it safely.

The creator needs a Twitch account connected to Fishrift.

The request

POST https://fishrift.com/webhooks/external-donations/{sourceId}
Content-Type: application/json
X-FishRift-Signature: sha256=<hex HMAC-SHA256 of the body>
X-FishRift-Timestamp: <unix time in seconds>

Body

FieldRequiredTypeNotes
event_idYesstringYour unique ID for this donation, 1 to 200 characters. Sending the same ID twice is safe
donor_nameYesstringThe name to show. Use something like "Anonymous" for anonymous donors
amountYesstringIn whole units, like "5.00". "5,00" also works. No thousands separators
currencyYesstring3 letters, like EUR
messageNostringThe donor's message

Signing

  1. Make the JSON body as a string.
  2. Calculate an HMAC-SHA256 of that exact string with your shared secret, as hex.
  3. Send it as X-FishRift-Signature: sha256=<hex>.
  4. Send the current unix time in seconds as X-FishRift-Timestamp. It must be within 5 minutes.

Send the exact bytes you signed. Changing whitespace after signing breaks the signature.

curl

SOURCE_ID="your-source-id"
SECRET="your-shared-secret"
BODY='{"event_id":"don_000123","donor_name":"Anonymous","amount":"5.00","currency":"EUR","message":"Keep it up!"}'
TS=$(date +%s)
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')

curl -X POST "https://fishrift.com/webhooks/external-donations/$SOURCE_ID" \
  -H "Content-Type: application/json" \
  -H "X-FishRift-Signature: sha256=$SIG" \
  -H "X-FishRift-Timestamp: $TS" \
  --data-raw "$BODY"

Node.js

import crypto from 'node:crypto';

const body = JSON.stringify({
  event_id: 'don_000123',
  donor_name: 'Anonymous',
  amount: '5.00',
  currency: 'EUR',
  message: 'Keep it up!',
});
const sig = crypto.createHmac('sha256', process.env.FISHRIFT_SECRET).update(body).digest('hex');

await fetch(`https://fishrift.com/webhooks/external-donations/${process.env.FISHRIFT_SOURCE_ID}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-FishRift-Signature': `sha256=${sig}`,
    'X-FishRift-Timestamp': String(Math.floor(Date.now() / 1000)),
  },
  body,
});

Responses

Fishrift answers right away and plays the alert after that.

StatusBodyMeaning
200{"status":"ok"}Accepted
200{"status":"already_processed"}This event_id was sent before. Treat it as success
400{"error":"invalid_..."}Something in the body is wrong. Fix it before sending again
403{"error":"bad_signature"}The signature doesn't match. Check the secret and the exact body
403{"error":"timestamp_out_of_window"}Your clock is off. Send again with a fresh timestamp
404{"error":"not_found"}The source ID doesn't exist or is switched off
429Too many requestsMore than 1000 requests in a minute

Retry on network errors and on 5xx responses. Because of event_id, a retry never plays the alert twice.

Currency

Fishrift doesn't convert currencies. Alerts show the amount with the creator's own currency symbol, so send amounts in the creator's currency when you can. The currency you send is kept in the event as raw.currency.

What the creator's widgets receive

{
  "id": "ext_src1_don_000123",
  "platform": "external",
  "type": "external_donation",
  "viewer_name": "Anonymous",
  "viewer_display_name": "Anonymous",
  "amount": 5,
  "message": "Keep it up!",
  "raw": { "source_name": "Your Platform", "currency": "EUR" },
  "timestamp": 1759600000000
}