Appearance
Clips from paid moments
When a fan pays for a shoutout, they want the second it was read out. Add clip_before_s and clip_after_s to the moment and Vabu cuts that clip from the live recording for you. When it is ready, you get a webhook with the fan's reference and the clip, and you send them the link.
You need a live stream, a secret key on your server and a webhook endpoint subscribed to streams.moment.clip_ready and streams.moment.clip_failed.
Ask for a clip
Post the moment as usual and add the window you want, in seconds around the moment. This one cuts 15 seconds before and 30 seconds after the shoutout.
sh
curl -X POST "https://api.vabu.live/v1/streams/live_streams/$STREAM/moments" \
-H "Authorization: Bearer $VABU_API_KEY" \
-H "Idempotency-Key: shoutout-pay_123" \
-H "Content-Type: application/json" \
-d '{ "type": "shoutout", "label": "Shoutout for Amina", "customer": "cust_77", "clip_before_s": 15, "clip_after_s": 30 }'The moment comes back with clip_status: "pending". A moment with neither field gets no clip.
What happens next
- When the stream goes live, Vabu saves the recording as an asset and notes the clock time of its first frame.
- Once the window has closed, plus a short wait for the video plane to finish its recording, Vabu cuts the window from the recording. It never cuts before the first frame or after the last. If the encoder dropped and came back, the time is placed by what was recorded, so the clip is the right moment.
- If the recording is not ready, Vabu tries again later: after 30 seconds, then 1, 2, 4, 8 and 10 minutes. After 8 tries the moment gets
clip_status: "failed"with aclip_error. - When the clip can be played you get
streams.moment.clip_ready. A cancelled moment gets no clip and itsclip_statusgoes back tonull.
Clips work on the standard plane and on Cloudflare Stream, and in test mode (simulated and ready at once). On the standard plane the clip is cut while the show runs. Cloudflare Stream cuts a finished recording only, so those clips arrive after the show ends, once the encoder has been gone for the reconnect window. Streams on Vabu Media cannot make clips yet.
Send the fan their clip
This handler checks the signature, then sends the link. It uses Express and Node 20 or later. phoneForCustomer and sendWhatsApp stand for your own code. customer is the reference you posted with the moment.
ts
import express from 'express'
import { verifyVabuSignature } from './verify-vabu-signature' // the function from the webhooks page
const app = express()
// Keep the raw body: the signature is over the exact bytes.
app.post('/vabu/webhooks', express.raw({ type: 'application/json' }), async (req, res) => {
const raw = req.body.toString()
if (!verifyVabuSignature(raw, req.get('Vabu-Signature') ?? '', process.env.VABU_WEBHOOK_SECRET!)) {
return res.sendStatus(400)
}
const event = JSON.parse(raw)
const moment = event.data.object
if (event.type === 'streams.moment.clip_ready') {
const phone = await phoneForCustomer(moment.customer)
const link = `https://example.com/clips/${moment.clip_asset}`
await sendWhatsApp(phone, `Your shoutout clip is ready: ${link}`)
}
if (event.type === 'streams.moment.clip_failed') {
console.warn(`No clip for ${moment.id}: ${moment.clip_error}`)
}
res.sendStatus(200)
})Your own page at /clips/{asset} asks Vabu for a playback token for that asset, as in paid access, so only the fan who paid can watch it.
Events can arrive more than once. Use event.id to skip one you have already handled.
Chapters on the replay
The recording of the show is an asset too. List the moments inside it with their place in the video:
sh
curl "https://api.vabu.live/v1/streams/assets/$RECORDING/moments" -H "Authorization: Bearer $VABU_API_KEY"Each moment has offset_s, the seconds from the start of the recording. Use it for chapter markers on a replay. The list is paginated like every other list and comes newest first, so sort by offset_s.
A vertical clip
POST /v1/streams/assets/{id}/clips accepts aspect: "9:16" for a clip to share on social. No video plane can render it yet, so the call returns 422 aspect_not_supported and no clip is made. Leave aspect out for the original shape.