Auto-process Supabase uploads with Ittybit

View Markdown

Upload a file to Supabase Storage and have it automatically processed by Ittybit. A database webhook triggers an Edge Function on insert, that function sends the file to Ittybit, and a webhook callback writes the results back to your Supabase database. Zero manual steps.

Architecture

  1. User uploads to Supabase Storage
  2. Database webhook on storage.objects insert fires an Edge Function
  3. Edge Function POSTs to Ittybit /tasks with the fileโ€™s public URL
  4. Ittybit processes the file and sends a webhook on completion
  5. A second Edge Function receives the webhook and upserts results into a media table

Create the media table

create table public.media (
  id uuid primary key default gen_random_uuid(),
  storage_path text not null,
  source_url text not null,
  task_id text,
  status text default 'pending',
  output_url text,
  metadata jsonb,
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

Edge Function: trigger processing

This function runs when a new row appears in storage.objects. It builds the public URL for the uploaded file and creates an Ittybit task.

// supabase/functions/process-upload/index.ts
import { serve } from "https://deno.land/std@0.177.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

serve(async (req) => {
const payload = await req.json();
const record = payload.record;

const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);

// Build the public URL for the uploaded file
const { data: urlData } = supabase.storage
.from(record.bucket_id)
.getPublicUrl(record.name);

const sourceUrl = urlData.publicUrl;

// Insert a pending row in the media table
const { data: media } = await supabase
.from("media")
.insert({
storage_path: `${record.bucket_id}/${record.name}`,
source_url: sourceUrl,
status: "pending",
})
.select()
.single();

// Create an Ittybit task
const task = await fetch("https://api.ittybit.com/jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${Deno.env.get("ITTYBIT_API_KEY")}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
input: sourceUrl,
kind: "video",
options: {
width: 1280,
format: "mp4",
quality: "high",
},
metadata: {
media_id: media.id,
},
}),
});

const result = await task.json();

// Store the task ID
await supabase
.from("media")
.update({ task_id: result.id, status: "processing" })
.eq("id", media.id);

return new Response(JSON.stringify({ ok: true }), {
headers: { "Content-Type": "application/json" },
});
});
# Test the flow manually by creating a task with a Supabase Storage URL
curl -X POST https://api.ittybit.com/jobs \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "https://your-project.supabase.co/storage/v1/object/public/uploads/video.mp4",
    "kind": "video",
    "options": {
      "width": 1280,
      "format": "mp4",
      "quality": "high"
    }
  }'

Wire up the database webhook

In the Supabase Dashboard, go to Database > Webhooks and create a new webhook:

  • Table: storage.objects
  • Events: INSERT
  • Type: Supabase Edge Function
  • Function: process-upload

Every file upload now triggers processing automatically.

Edge Function: receive Ittybit webhook

This function receives the callback from Ittybit when processing completes and updates your media table.

// supabase/functions/ittybit-webhook/index.ts
import { serve } from "https://deno.land/std@0.177.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

serve(async (req) => {
const payload = await req.json();

const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);

const mediaId = payload.metadata?.media_id;
if (!mediaId) {
return new Response("Missing media_id", { status: 400 });
}

await supabase
.from("media")
.update({
status: payload.status === "completed" ? "completed" : "failed",
output_url: payload.output?.url ?? null,
metadata: payload,
updated_at: new Date().toISOString(),
})
.eq("id", mediaId);

return new Response(JSON.stringify({ ok: true }), {
headers: { "Content-Type": "application/json" },
});
});
# Register your webhook endpoint in the Ittybit dashboard
# or via the API:
curl -X POST https://api.ittybit.com/webhooks \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-project.supabase.co/functions/v1/ittybit-webhook",
    "events": ["job.succeeded", "job.failed"]
  }'

Deploy the Edge Functions

supabase functions deploy process-upload
supabase functions deploy ittybit-webhook

Set your secrets:

supabase secrets set ITTYBIT_API_KEY=your_ittybit_api_key

SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are available automatically in Edge Functions.

Test the pipeline

Upload a file to your Supabase Storage bucket and query the media table to watch it progress:

select id, storage_path, status, output_url, created_at
from media
order by created_at desc
limit 5;

The row should move from pending to processing to completed, with output_url populated once Ittybit finishes.

See also