Gondola integration — video posts end to end
Gondola is Energixer's companion service: a multi-tenant social/forum backend. Gondola stores references to Energixer videos and never holds your Energixer key — the integration is one shared webhook secret.
The flow
Upload (your backend + the device):
POST /v1/videoson Energixer → hand theupload_tokento the device → device uploads via tus.Post immediately (your backend → Gondola): create the post with
video: {energixer_video_id}while the video is still processing. Gondola'svideo_post_visibilityconfig decides whether the post shows a processing placeholder or stays hidden until ready.Readiness (Energixer → Gondola, direct): point your Energixer domain's
webhook_urlat Gondola's receiver —https://<your-gondola>/v1/webhooks/energixer— and paste the samewhsec_secret into both dashboards:- Energixer:
PATCH /v1/domain/config→webhook_url,webhook_secret - Gondola: domain setting
energixer_webhook_secret
Gondola verifies the
t=/v1=HMAC signature, deduplicates byEnergixer-Delivery-Id, matches the video by(domain, energixer_video_id), stores the playback URLs from the payload, and flips the post live. Onvideo.failed, Gondola applies your configured fallback (publish text-only vs. keep hidden).- Energixer:
Playback: Gondola hands your clients the Energixer URLs in its feed payloads; devices stream directly from Energixer. Gondola never proxies video bytes.
Those URLs need a playback key. Energixer videos are private by default, so a device that fetches a feed URL with no credential gets
401 playback_key_required. Gondola deliberately holds noek_key, which means it cannot mint one for you — minting is your backend's job. See the pattern below.
Playback keys: one per viewer session
Mint one domain-scoped key per viewer session. A key covers the whole domain and lives an hour by default, so one covers every card in the feed, every page of pagination, and every prefetch after it. That is one extra call per session — not per card, not per page. The client's only ongoing job is re-minting when it expires or when a fetch returns 401.
device → your backend: "give me the feed"
backend → Gondola: GET /v1/swipe (gk_ key + X-Gondola-User)
backend → Energixer: POST /v1/playback-keys (ek_ key)
{ "viewer_ref": "<your stable user id>" }
→ { playback_key, viewer_ref, expires_at, video_ids }
backend → device: { items: [...], playback_key, expires_at }
device: attaches Authorization: Bearer <playback_key>
to its Energixer fetches
Your backend, once per session:
const res = await fetch(`${ENERGIXER}/v1/playback-keys`, {
method: 'POST',
headers: {
Authorization: `Bearer ${ENERGIXER_API_KEY}`, // ek_ — backend only
'Content-Type': 'application/json',
},
// Your STABLE user id, not a random per-session value — see below.
body: JSON.stringify({ viewer_ref: user.id }),
});
const { playback_key, expires_at } = await res.json();
// Ship playback_key + expires_at down with the feed payload.
Web (hls.js) — the header goes on every manifest and segment request:
const hls = new Hls({
xhrSetup: (xhr) =>
xhr.setRequestHeader('Authorization', `Bearer ${playbackKey}`),
});
hls.loadSource(ENERGIXER + item.video.hls_url);
hls.attachMedia(videoEl);
React Native — react-native-video can send headers itself, so it needs none
of the browser workarounds below:
<Video
source={{
uri: ENERGIXER + item.video.hls_url,
headers: { Authorization: `Bearer ${playbackKey}` },
}}
controls
/>
Five things to get right
viewer_refmust be your stable per-user id. It is the revocation handle:POST /v1/playback-keys/revoke {viewer_ref}is the only targeted cutoff, andrevoke-allbumps the epoch for every viewer on the domain. A random per-sessionviewer_refmakes that user's keys unrevocable in practice — they simply live out their TTL.- Refresh from
expires_at, not from an assumed hour.ttl_secondsis caller-settable (60 s … 24 h), so read the value you were given back. - Omit
video_idsfor a feed. It exists to narrow a key for an embed or a share link. The cap is 20 ids and Gondola's swipe feed pages up to 50, so a video-scoped key cannot express even one page — let alone the prefetch. The domain-scoped default is the workable feed shape. - Add your web origin to
cors_origins(PATCH /v1/domain/config) for browser playback. A correct key with the origin missing fails as an opaque CORS error, which reads convincingly like "the key is wrong". Both prerequisites, in that order. og_thumbnail_urldoes not work for private videos, and cannot. It is generated to be an OpenGraph image, and Slack/iMessage/Twitter crawlers will never send anAuthorizationheader. If link previews matter for a piece of content, mark that video"visibility": "public"at initiate — there is no workaround at the key layer.
Posters (<img>) and caption tracks (<track>) have the same header problem
in a browser; fetch them yourself and hand the element a blob URL. The
playback guide has the full list.
Alternative: relay mode
If you prefer your backend in the loop, receive the webhook yourself and
relay via Gondola's PATCH /v1/videos/:id with the URLs from the payload.
Same data, one extra hop, your code owns the policy.
Checklist
- Energixer
webhook_url→ Gondola receiver URL - Same
whsec_in both systems (rotate on both within the same hour — Energixer dual-signs during the grace window) - Post created with
energixer_video_idat upload time - Gondola
video_post_visibilityset to your product's taste - A key-minting endpoint on your backend (
POST /v1/playback-keyswith yourek_key), returningplayback_key+expires_atalongside the feed - Your web origin in the Energixer domain's
cors_origins(browser playback only — native apps do not need it)