Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Handle Gamma webhooks and events for real-time updates. Use when implementing webhook receivers, processing events, or building real-time Gamma integrations. Trigger with phrases like "gamma webhooks", "gamma events", "gamma notifications", "gamma real-time", "gamma callbacks".
.claude/skills/jeremylongshore-gamma-webhooks-events/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 50% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 99% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 74% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 59% | 0% |
Gamma's public API (v1.0) is generation-focused and does not expose a traditional webhook system at time of writing. Instead, use the poll-based pattern (GET /v1.0/generations/{id}) to detect completion. For event-driven architectures, wrap polling in a background worker that emits application-level events when generations complete or fail.
gamma-sdk-patterns setupSince Gamma does not push events, you create them by polling:
| Synthetic Event | Trigger Condition | Use Case | |-----------------|-------------------|----------| | generation.started | POST /generations returns generationId | Log, notify user | | generation.completed | Poll returns status: "completed" | Download export, update DB | | generation.failed | Poll returns status: "failed" | Alert, retry, notify user | | generation.timeout | Poll exceeds max duration | Alert, escalate |
typescript// src/gamma/events.ts import { EventEmitter } from "events"; import { createGammaClient } from "./client"; export const gammaEvents = new EventEmitter(); export interface GenerationEvent { generationId: string; status: "started" | "completed" | "failed" | "timeout"; gammaUrl?: string; exportUrl?: string; creditsUsed?: number; error?: string; } export async function generateWithEvents( content: string, options: { outputFormat?: string; exportAs?: string; themeId?: string } = {} ): Promise<GenerationEvent> { const gamma = createGammaClient({ apiKey: process.env.GAMMA_API_KEY! }); // Start generation const { generationId } = await gamma.generate({ content, outputFormat: options.outputFormat ?? "presentation", exportAs: options.exportAs, themeId: options.themeId, }); gammaEvents.emit("generation", { generationId, status: "started", } as GenerationEvent); // Poll for completion const deadline = Date.now() + 180000; // 3 minute timeout while (Date.now() < deadline) { const result = await gamma.poll(generationId); if (result.status === "completed") { const event: GenerationEvent = { generationId, status: "completed", gammaUrl: result.gammaUrl, exportUrl: result.exportUrl, creditsUsed: result.creditsUsed, }; gammaEvents.emit("generation", event); return event; } if (result.status === "failed") { const event: GenerationEvent = { generationId, status: "failed", error: "Generation failed", }; gammaEvents.emit("generation", event); return event; } await new Promise((r) => setTimeout(r, 5000)); } const timeoutEvent: GenerationEvent = { generationId, status: "timeout", error: "Poll timeout after 180s", }; gammaEvents.emit("generation", timeoutEvent); return timeoutEvent; }
typescript// src/gamma/listeners.ts import { gammaEvents, GenerationEvent } from "./events"; // Log all events gammaEvents.on("generation", (event: GenerationEvent) => { console.log(`[Gamma] ${event.status}: ${event.generationId}`); }); // Handle completed generations gammaEvents.on("generation", async (event: GenerationEvent) => { if (event.status === "completed") { // Download export file if (event.exportUrl) { const res = await fetch(event.exportUrl); const buffer = Buffer.from(await res.arrayBuffer()); // Save to S3, send to user, etc. console.log(`Downloaded export: ${buffer.length} bytes`); } // Update database await db.generations.update({ where: { generationId: event.generationId }, data: { status: "completed", gammaUrl: event.gammaUrl }, }); } }); // Handle failures gammaEvents.on("generation", async (event: GenerationEvent) => { if (event.status === "failed" || event.status === "timeout") { // Alert team await sendSlackAlert(`Gamma generation ${event.generationId} ${event.status}: ${event.error}`); } });
typescript// src/workers/gamma-worker.ts import Bull from "bull"; import { createGammaClient } from "../gamma/client"; const generationQueue = new Bull("gamma-generations", process.env.REDIS_URL!); // Producer: queue generation requests export async function queueGeneration(content: string, options: any = {}) { return generationQueue.add( { content, ...options }, { attempts: 2, backoff: { type: "exponential", delay: 10000 } } ); } // Consumer: process in background generationQueue.process(3, async (job) => { const gamma = createGammaClient({ apiKey: process.env.GAMMA_API_KEY! }); const { content, outputFormat, exportAs } = job.data; const { generationId } = await gamma.generate({ content, outputFormat: outputFormat ?? "presentation", exportAs, }); // Poll until done const deadline = Date.now() + 180000; while (Date.now() < deadline) { await job.progress(Math.min(90, ((Date.now() - (deadline - 180000)) / 180000) * 100)); const result = await gamma.poll(generationId); if (result.status === "completed") return result; if (result.status === "failed") throw new Error("Generation failed"); await new Promise((r) => setTimeout(r, 5000)); } throw new Error("Poll timeout"); }); generationQueue.on("completed", (job, result) => { console.log(`Generation completed: ${result.gammaUrl}`); }); generationQueue.on("failed", (job, err) => { console.error(`Generation failed: ${err.message}`); });
If you want true webhook-style push notifications for integrations:
typescript// src/gamma/callback.ts // After generation completes, POST results to a configured URL async function notifyCallback(callbackUrl: string, event: GenerationEvent) { await fetch(callbackUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ event: `generation.${event.status}`, data: event, timestamp: new Date().toISOString(), }), }); } // Usage: register a callback when starting a generation const result = await generateWithEvents("My presentation content"); if (result.status === "completed") { await notifyCallback("https://your-app.com/hooks/gamma", result); }
| Issue | Cause | Solution | |-------|-------|----------| | Poll timeout | Generation taking too long | Increase timeout beyond 3 min for complex content | | Missed completion | Poll interval too large | Use 5s interval (Gamma recommendation) | | Duplicate processing | No idempotency check | Track processed generationIds in a Set or DB | | Export URL expired | Downloaded too late | Download immediately on completion |
Proceed to gamma-performance-tuning for optimization.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 22,862 | 24,337 | +6% | 1 | 1 | 0% | 4,814 | 6,338 | +32% | 0 | 0 | — |
case-02 | fail→fail | 26,113 | 24,152 | -8% | 1 | 1 | 0% | 4,197 | 5,937 | +41% | 0 | 0 | — |
case-03 | fail→fail | 20,610 | 18,616 | -10% | 1 | 1 | 0% | 3,979 | 5,901 | +48% | 0 | 0 | — |
case-04 | fail→fail | 18,678 | 10,482 | -44% | 1 | 1 | 0% | 2,489 | 3,903 | +57% | 0 | 0 | — |
case-05 | fail→pass | 17,147 | 6,622 | -61% | 1 | 1 | 0% | 2,250 | 3,092 | +37% | 0 | 0 | — |
case-06 | pass→pass | 14,452 | 8,692 | -40% | 1 | 1 | 0% | 1,478 | 2,572 | +74% | 0 | 0 | — |
case-07 | pass→pass | 8,226 | 6,878 | -16% | 1 | 1 | 0% | 1,406 | 2,292 | +63% | 0 | 0 | — |
case-08 | fail→pass | 17,190 | 8,639 | -50% | 1 | 1 | 0% | 2,485 | 3,734 | +50% | 0 | 0 | — |
case-09 | fail→fail | 19,471 | 17,692 | -9% | 1 | 1 | 0% | 2,402 | 4,250 | +77% | 0 | 0 | — |
case-10 | fail→fail | 21,599 | 13,441 | -38% | 1 | 1 | 0% | 3,110 | 4,529 | +46% | 0 | 0 | — |
case-11 | pass→pass | 15,034 | 4,036 | -73% | 1 | 1 | 0% | 1,871 | 2,741 | +46% | 0 | 0 | — |
case-12 | pass→pass | 17,686 | 13,658 | -23% | 1 | 1 | 0% | 2,326 | 4,127 | +77% | 0 | 0 | — |
case-13 | pass→pass | 17,570 | 12,856 | -27% | 1 | 1 | 0% | 2,422 | 3,506 | +45% | 0 | 0 | — |
case-14 | fail→pass | 13,237 | 9,748 | -26% | 1 | 1 | 0% | 1,467 | 2,926 | +99% | 0 | 0 | — |
case-15 | pass→pass | 15,056 | 16,964 | +13% | 1 | 1 | 0% | 2,976 | 4,404 | +48% | 0 | 0 | — |
case-16 | fail→pass | 13,851 | 10,825 | -22% | 1 | 1 | 0% | 1,913 | 3,330 | +74% | 0 | 0 | — |
case-17 | fail→pass | 18,823 | 11,925 | -37% | 1 | 1 | 0% | 2,892 | 4,593 | +59% | 0 | 0 | — |
case-18 | fail→pass | 15,510 | 10,566 | -32% | 1 | 1 | 0% | 2,006 | 3,215 | +60% | 0 | 0 | — |
case-19 | pass→pass | 22,221 | 7,210 | -68% | 1 | 1 | 0% | 2,409 | 3,521 | +46% | 0 | 0 | — |
case-20 | fail→pass | 14,587 | 5,444 | -63% | 1 | 1 | 0% | 1,629 | 3,166 | +94% | 0 | 0 | — |
case-21 | pass→pass | 18,101 | 8,512 | -53% | 1 | 1 | 0% | 2,047 | 3,362 | +64% | 0 | 0 | — |
case-22 | fail→pass | 26,597 | 28,237 | +6% | 1 | 1 | 0% | 4,122 | 6,510 | +58% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.