Docs
SDKs
The gift tray, the gift effects and a small client for your app, on five platforms. All five follow one shared contract and run the same test fixtures for the logic: prices, tiers, combos, the client and the room protocol. The screens (tray, effects, wall) are drawn natively on each platform and are not covered by those fixtures, so they can differ in small ways.
Before you start
- License. The SDK code is MIT licensed. The GiftingStack name and logo, the service and the ready-made gift library are not part of that licence.
-
A token from your server. Every SDK asks your server for a client
token, which your server creates with
POST /v1/client-tokensand the secret key. The secret key never goes into the app. See Quickstart, step 4. - Your own look. Use the ready tray, keep our logic and draw your own widgets, or anything in between: Integration patterns. Colours, texts, placement, motion and effect tiers: Customize the SDK.
- Live rooms work on every platform: Gift rooms.
Web
@giftingstack/sdk/web. TypeScript, works with any framework. Also as the custom elements
<gift-tray>, <gift-effect-layer>,
<gift-wall>, <gift-away-summary>,
<gift-leaderboard>, <gift-room> and
<gift-battle-bar> (web only).
import { createGiftingClient } from "@giftingstack/sdk";
import { GiftEffectLayer, createGiftTray } from "@giftingstack/sdk/web";
const client = createGiftingClient({
getToken: async () => (await fetch("/api/gift-token")).text(),
});
const screen = document.querySelector("#screen");
const effects = new GiftEffectLayer(screen);
const tray = createGiftTray({ container: screen, client, receiver: post.authorId, effects });
// When the screen goes away:
tray.destroy();
Try it: the live web demo.
React Native and Expo
@giftingstack/sdk/react-native. Needs react 19.1 or newer and
react-native 0.80 or newer.
import { useRef } from "react";
import { createGiftingClient } from "@giftingstack/sdk";
import { GiftEffectLayer, GiftTray, useGiftTray } from "@giftingstack/sdk/react-native";
const client = createGiftingClient({
getToken: async () => (await fetch("https://your.app/api/gift-token")).text(),
});
function PostScreen({ post }) {
const effects = useRef(null);
const tray = useGiftTray({ client, receiver: post.authorId, effects, reference: post.id });
return (
<View style={{ flex: 1 }}>
{/* your screen */}
<GiftTray tray={tray} />
<GiftEffectLayer ref={effects} />
</View>
);
}
Flutter
Package giftingstack. Dart 3.6 or newer; depends on Flutter
and http only.
final client = GiftingClient(
getToken: ({required bool forceRefresh}) =>
myBackend.giftingToken(forceRefresh: forceRefresh),
);
final effects = GiftEffectController();
final tray = GiftTrayNotifier(client: client, receiver: 'mateo', effects: effects);
Stack(children: [
YourScreen(),
Positioned.fill(
child: GiftTray(tray: tray),
),
Positioned.fill(child: GiftEffectLayer(controller: effects)),
]);
Android
Kotlin and Jetpack Compose, minSdk 24. It brings kotlinx-coroutines and kotlinx-serialization (both
used in its public API) and nothing else outside AndroidX and Compose.
val client = GiftingClient(getToken = { forceRefresh -> myBackend.giftingToken(forceRefresh) })
@Composable
fun PostScreen(client: GiftingApi) {
val effects = rememberGiftEffectLayerState()
val tray = rememberGiftTrayHolder(client, receiver = "mateo", effects = effects)
Box(Modifier.fillMaxSize()) {
/* your screen */
GiftTray(tray = tray)
GiftEffectLayer(effects)
}
}
iOS
Swift and SwiftUI, iOS 15+. Swift package, product GiftingStack .
import GiftingStack
import SwiftUI
let client = GiftingClient(getToken: { forceRefresh in
try await MyBackend.shared.giftingToken(forceRefresh: forceRefresh)
})
struct PostScreen: View {
@StateObject private var effects = GiftEffectController()
@StateObject private var tray = GiftTrayModel(client: client, receiver: "mateo")
var body: some View {
ZStack {
PostView()
GiftTray(tray: tray)
GiftEffectLayer(controller: effects).ignoresSafeArea()
}
.onAppear { tray.effects = effects; tray.start() }
.onDisappear { tray.stop() }
}
}
What each platform has
Same names and behaviour wherever a row says yes. The table scrolls sideways on a phone.
Web RN Flutter Android iOS
Gift tray (sheet) yes yes yes yes yes
Gift button + keyboard yes yes yes yes yes
Battle "Send to" picker yes yes yes yes yes
Tray as popover / inline yes - - - -
Effect layer yes yes yes yes yes
Gift wall yes yes yes yes yes
While you were away yes yes yes yes yes
Leaderboard yes yes yes yes yes
Rooms (join, effects) yes yes yes yes yes
Battle scores (data) yes yes yes yes yes
Battle bar (drawn) yes yes yes yes yes
Render slots yes yes yes yes yes
Test mode badge yes yes yes yes yes
Theme from one colour yes yes yes yes yes
Custom elements (tags) yes - - - -
Render slots are renderChip (wall), renderRow (leaderboard) and
renderSummary (away), to draw your own item inside our container. The battle
bar (GiftBattleBar and <gift-battle-bar> on web,
BattleBar on React Native, GiftBattleBar on Flutter, Android and
iOS) draws a versus round for you: both receivers with their scores, a countdown and a
split bar that slides as gifts arrive; it follows the theme and reduced motion, and the
numbers behind it (split, clock, short scores, colours) are one shared test file that all
five SDKs pass. You can still draw your own bar from the battle view. The popover and
inline tray placements exist on web only; the other platforms use the bottom sheet.
"Gift button + keyboard" is GiftButton for your own message bar with the tray
taking the keyboard's place (how).
What all SDKs do
-
Test mode badge. The tray shows a "Test mode" badge by itself while
GET /v1/client/meanswerslivemode: false, that is, while the token was made with a test key. Pass the state holder to the tray: React NativeGiftTray tray={useGiftTray(...)}, FlutterGiftTray(tray: tray), AndroidGiftTray(tray = holder), iOSGiftTray(tray: model); the web tray does it itself.testModeis optional: unset follows the state,falseforces the badge off,trueforces it on. What you pass yourself (gifts,balance,onSend) wins overtray. -
Encrypted connections only. A
baseUrlthat starts withhttp://(or a roomsurlwithws://) is refused at once (an argument error; on iOS apreconditionthat stops the app, so catch it in development), because the token travels in every request. Hostslocalhost,127.0.0.1,::1and10.0.2.2(the Android emulator) are allowed; for any other plain-text host passallowInsecure(<gift-room allow-insecure>on the web element). -
Timeouts and errors. Each request attempt gives up after
the timeout option: 20 seconds by default on web and React Native (
timeoutMs, 0 turns it off), Flutter (timeout, aDuration;Duration.zeroturns it off) and iOS (timeoutin seconds; 0 uses the system default); Android uses 15 seconds to connect and 30 to read). A timeout or a lost connection is aGiftingErrorwith status0and typenetwork_error. A 2xx answer whose body is not the JSON the SDK expects is typeinvalid_responsewith the HTTP status; it carries none of the body and is not retried. A send that got no answer at all is repeated once with the same idempotency key, so a gift is never charged twice. -
Pictures. Only
https://,data:image/andhttp://to the loopback hosts above are loaded (the web and React Native also load paths on your own origin); any other URL is not requested and the placeholder is drawn. Flutter, Android and iOS stop a picture at 5 MiB. A picture that failed is not requested again for 60 seconds. The tray loads every gift's small icon when the catalog arrives and the big picture only for the selected gift. -
Web Content-Security-Policy. The web SDK adds no inline script, no
evaland nostyleattributes. Allowconnect-src https://api.giftingstack.com wss://rt.giftingstack.com https://assets.giftingstack.com(the API, rooms, remote recipes) andimg-src https://assets.giftingstack.complus your own picture hosts (anddata:for data URLs).style-srcneeds nothing in browsers with constructable stylesheets (Chrome 73, Firefox 101, Safari 16.4); older ones get a<style>element that needs your nonce:configureStyles({ nonce })from@giftingstack/sdk/web, called once before the first component, or a<meta name="csp-nonce" content="...">tag. -
Rooms reconnect. After 12 failed attempts in a row the room state is
closedwith reasonretries_exhausted; a suspended project closes with4011;reconnectNow()starts again. Details: Gift rooms.
Also in the SDKs
- Gift wall, while you were away and leaderboard components that use the same client and theme.
- Themes from one accent colour (light, dark or your own), reduced motion, a low-quality mode for slow phones, and texts you can translate.
- A "Test mode" badge on the tray while the token comes from a test key, and an "Get coins" button that opens your own coin store when the balance is too low.
- Without an SDK, the API and the gift pictures work from any language: see API or SDK?