{"id":"a9299fad-cd5e-4d42-9514-81006ced73ea","shortId":"SwhjTa","kind":"skill","title":"inngest-realtime","tagline":"Use when streaming durable workflow updates to a UI in real time — live order status pages that animate as steps complete, AI agent token streaming from a function to the browser, log tailing for long-running jobs, or human-in-the-loop approval flows that publish a prompt and wai","description":"# Inngest Realtime\n\nStream updates from durable Inngest functions to live UIs. Use channels and topics to broadcast progress, render workflow execution as it happens, or build bi-directional human-in-the-loop flows.\n\n> **These skills are focused on TypeScript.** For Python or Go, refer to the [Inngest documentation](https://www.inngest.com/llms.txt) for language-specific guidance. Core concepts apply across all languages.\n\n> **⚠ CRITICAL: v3 vs v4 package selection**\n>\n> Realtime in Inngest v4 lives at the SDK subpath `inngest/realtime`. The standalone `@inngest/realtime` npm package is a **v3-era package** and is **NOT compatible with `inngest@4.x`**. If your project is on v4 (the npm default), do not install `@inngest/realtime`. Use the imports below.\n>\n> Symptoms of using the wrong package on v4: `TypeError: Cls is not a constructor` on every `PUT /api/inngest`, 401 on subscription tokens, type incompatibility on `new Inngest({ middleware: [...] })`. Verify your `package.json` shows `\"inngest\": \"^4.x\"` before reading further.\n\n## Prerequisites\n\n- Inngest v4 SDK installed (`npm install inngest`) — see the `inngest-setup` skill\n- `INNGEST_DEV=1` set in `.env.local` for local development (without it, the SDK demands cloud signing keys and 401s on token requests)\n- Local Inngest dev server running (`npx inngest-cli@latest dev`)\n- Optional: `zod` for schema validation on topics\n\n## When to use Realtime\n\n| Problem shape | Pattern |\n|---|---|\n| Order status page animates as durable workflow steps complete | Per-run channel, publish per step, client subscribes |\n| AI agent streams tokens to a chat UI | Per-conversation channel, publish chunks, stream to browser |\n| Log tail for a long-running job | Single channel, log topic, append to UI |\n| Human-in-the-loop approval | Channel + waitForEvent, publish prompt, wait for response |\n| Admin dashboard with live order list | Global admin channel, fan-out from each function |\n\n## Architecture\n\nThree pieces:\n\n1. **Channel definition** — a typed contract for what gets published. Lives in shared module so both server and client can reference the same channel name.\n2. **Publishing** — call `step.realtime.publish` between steps to wrap a durable publish, or `inngest.realtime.publish` inside `step.run` because you're already inside a memoized step. See \"Which publish method to use\" below.\n3. **Subscribing** — server action mints a subscription token; React client uses the `useRealtime` hook (or the lower-level `subscribe()` API for non-React consumers).\n\n## Step 1: Define a channel\n\nChannels are pure data — no class hierarchy, no zod runtime required (but recommended for type safety). Define them once and import where needed.\n\n```typescript\n// src/inngest/channels.ts\nimport { channel } from 'inngest/realtime';\nimport { z } from 'zod';\n\n// Per-run channel: each fulfill-order run publishes step updates to its own channel.\nexport const orderChannel = channel({\n  name: (orderId: string) => `order:${orderId}`,\n  topics: {\n    step: {\n      schema: z.object({\n        name: z.string(),\n        status: z.enum(['running', 'complete', 'failed']),\n        output: z.record(z.string(), z.unknown()).optional(),\n        ts: z.number(),\n      }),\n    },\n  },\n});\n\n// Global admin channel: fan-out for cross-cutting visibility.\nexport const adminChannel = channel({\n  name: 'admin',\n  topics: {\n    order: {\n      schema: z.object({\n        orderId: z.string(),\n        step: z.string(),\n        status: z.enum(['running', 'complete', 'failed']),\n        ts: z.number(),\n      }),\n    },\n  },\n});\n```\n\n**Two channel name shapes:**\n- `name: 'admin'` — static channel, accessed as `adminChannel.order` (topic ref)\n- `name: (id) => 'channel:${id}'` — parametric, accessed as `orderChannel(id).step` (call the channel def with the id, then access topic)\n\n## Step 2: Publish from inside a function\n\nInngest v4 ships realtime support natively — **no middleware required.** But where you call `publish` matters: it determines whether the publish is durable, and it's the most common place to get realtime wrong.\n\n### Which publish method to use\n\n| Where you are | Use this | Why |\n|---|---|---|\n| **Outside a step** (top-level handler code, between `step.run` calls) | `step.realtime.publish(id, topicRef, data)` | Wraps the publish in its own step so it's durable, deduplicated by `id`, and retry-safe. |\n| **Inside a step** (inside the callback passed to `step.run`) | `inngest.realtime.publish(topicRef, data)` | You're already inside a memoized step. `step.realtime.publish` would create a step inside a step. The bare client publish is the right call here. |\n| **Outside a function** (one-off route, script, etc.) | `inngest.realtime.publish(topicRef, data)` | Allowed, but **not retry-safe** — your client receiver must handle duplicates. |\n\nThe 90% rule: if you're writing handler code and you reach for `publish`, use `step.realtime.publish`. If you're writing code inside a `step.run` block and you reach for `publish`, use `inngest.realtime.publish`.\n\n### Example: both patterns in one function\n\n```typescript\n// src/inngest/functions/fulfill-order.ts\nimport { inngest } from '../client';\nimport { orderChannel, adminChannel } from '../channels';\n\nexport const fulfillOrder = inngest.createFunction(\n  {\n    id: 'fulfill-order',\n    retries: 3,\n    triggers: [{ event: 'store/order.placed' }],\n  },\n  async ({ event, step }) => {\n    const { orderId, customerEmail, lineItems } = event.data;\n\n    // Outside any step.run — use step.realtime.publish for a durable wrapper.\n    const emit = async (\n      name: string,\n      status: 'running' | 'complete' | 'failed',\n      output?: Record<string, unknown>,\n    ) => {\n      const ts = Date.now();\n      await step.realtime.publish(\n        `emit-order-${name}-${status}`,\n        orderChannel(orderId).step,\n        { name, status, output, ts },\n      );\n      await step.realtime.publish(\n        `emit-admin-${name}-${status}`,\n        adminChannel.order,\n        { orderId, step: name, status, ts },\n      );\n    };\n\n    await emit('capture-payment', 'running');\n\n    // Inside step.run — use inngest.realtime.publish (already in a memoized step).\n    const payment = await step.run('capture-payment', async () => {\n      const intent = await stripe.paymentIntents.create({ /* ... */ });\n\n      // Stream a partial update mid-step. No step-in-step wrapping needed.\n      await inngest.realtime.publish(orderChannel(orderId).step, {\n        name: 'capture-payment',\n        status: 'running',\n        output: { stage: 'intent-created', intentId: intent.id },\n        ts: Date.now(),\n      });\n\n      return await stripe.paymentIntents.confirm(intent.id);\n    });\n\n    await emit('capture-payment', 'complete', payment);\n\n    await emit('reserve-inventory', 'running');\n    const inventory = await step.run('reserve-inventory', async () => {\n      // ...\n    });\n    await emit('reserve-inventory', 'complete', inventory);\n\n    // ...\n  },\n);\n```\n\n**Why no middleware:** Earlier versions used `@inngest/realtime`'s `realtimeMiddleware()` to inject a `publish` arg into the handler. v4 puts it on `step.realtime` and `inngest.realtime` directly.\n\n## Step 3: Mint a subscription token (server action)\n\nIn Next.js App Router, use a Server Action to securely mint a short-lived token for the React hook in Step 4. Without a token, clients can't subscribe.\n\n```typescript\n// src/app/orders/[orderId]/actions.ts\n'use server';\n\nimport { getClientSubscriptionToken } from 'inngest/react';\nimport { inngest } from '@/inngest/client';\nimport { orderChannel } from '@/inngest/channels';\n\nexport async function fetchOrderSubscriptionToken(orderId: string) {\n  // ⚠ AUTHORIZATION GATE: verify the current user owns this orderId\n  // before minting a token. Channels are addressable by ID, so without\n  // an ownership check, anyone can subscribe to any order's stream by\n  // guessing IDs.\n  //\n  //   const session = await getServerSession();\n  //   if (!session) throw new Error('Unauthenticated');\n  //   const order = await db.order.findUnique({ where: { id: orderId } });\n  //   if (order?.userId !== session.userId) throw new Error('Forbidden');\n\n  return getClientSubscriptionToken(inngest, {\n    channel: orderChannel(orderId),\n    topics: ['step'],\n  });\n}\n```\n\n`getClientSubscriptionToken` from `inngest/react` returns a token shape that the `useRealtime` hook in Step 4 consumes directly. No ChannelInstance stripping needed — that gotcha only applies to the lower-level `getSubscriptionToken` + manual `subscribe()` path (see \"Pattern: Manual subscribe\" below).\n\n## Step 4: Subscribe with the `useRealtime` hook\n\nThe recommended consumer for React/Next.js is the `useRealtime` hook from `inngest/react`. It handles the subscription lifecycle, reconnect, type narrowing per topic, and cleanup.\n\n```typescript\n// src/components/OrderStatusClient.tsx\n'use client';\n\nimport { useRealtime } from 'inngest/react';\nimport { orderChannel } from '@/inngest/channels';\nimport { fetchOrderSubscriptionToken } from '@/app/orders/[orderId]/actions';\n\nexport function OrderStatusClient({ orderId }: { orderId: string }) {\n  const { messages, connectionStatus, error } = useRealtime({\n    channel: orderChannel(orderId),\n    topics: ['step'] as const,\n    token: () => fetchOrderSubscriptionToken(orderId),\n  });\n\n  if (error) return <div>Error: {error.message}</div>;\n\n  return (\n    <div>\n      <div>Status: {connectionStatus}</div>\n      <ul>\n        {messages.all.map((m, i) => (\n          <li key={i}>\n            {(m.data as { name: string }).name}: {(m.data as { status: string }).status}\n          </li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n```\n\n**Useful options on the hook:**\n\n| Option | Default | Use it when |\n|---|---|---|\n| `enabled` | `true` | Delay the subscription until you have an ID (e.g., `enabled: !!runId`). |\n| `bufferInterval` | `0` | Batch updates from a fast stream so React doesn't re-render per message. |\n| `pauseOnHidden` | `false` | Pause the stream when the tab isn't visible (saves bandwidth). |\n| `autoCloseOnTerminal` | `true` | Disconnect when the run completes — turn off to keep the stream open for fan-out channels. |\n| `historyLimit` | unbounded | Cap how many messages are retained in `messages.all`. |\n\nThe hook returns `messages.byTopic` (latest per topic), `messages.all` (full history), `messages.last` (most recent), and `messages.delta` (new since last render).\n\n## Pattern: Manual subscribe (non-React or custom transport)\n\nThe `useRealtime` hook covers the React case. If you're not using React, or you need a custom subscription lifecycle (server-side streaming, background workers, custom protocols), use the lower-level `subscribe()` API directly.\n\n### Server action: mint a token with the lower-level helper\n\n```typescript\n// src/app/orders/[orderId]/actions.ts\n'use server';\n\nimport { getSubscriptionToken } from 'inngest/realtime';\nimport { inngest } from '@/inngest/client';\nimport { orderChannel } from '@/inngest/channels';\n\nexport async function fetchOrderSubscriptionTokenLowLevel(orderId: string) {\n  // ⚠ AUTHORIZATION GATE: same as Step 3 — verify ownership before minting.\n\n  const token = await getSubscriptionToken(inngest, {\n    channel: orderChannel(orderId),\n    topics: ['step'],\n  });\n\n  // ⚠ CRITICAL: strip the ChannelInstance from the response.\n  // getSubscriptionToken returns { channel: ChannelInstance, ... } where\n  // ChannelInstance contains zod schema methods (a class with prototypes).\n  // Next.js refuses to serialize classes across the server-action → client-component\n  // boundary, so return ONLY primitives.\n  return {\n    channel: orderChannel(orderId).name as string,\n    topics: ['step'] as const,\n    key: token.key,\n    apiBaseUrl: token.apiBaseUrl,\n  };\n}\n```\n\n### Manual client subscription\n\n```typescript\n// src/components/OrderStatusManual.tsx\n'use client';\n\nimport * as React from 'react';\nimport { subscribe } from 'inngest/realtime';\nimport { fetchOrderSubscriptionTokenLowLevel } from '@/app/orders/[orderId]/actions';\n\nexport function OrderStatusManual({ orderId }: { orderId: string }) {\n  const [messages, setMessages] = React.useState<unknown[]>([]);\n\n  React.useEffect(() => {\n    let cancelled = false;\n    let sub: { close?: (reason?: string) => void } | undefined;\n\n    (async () => {\n      const token = await fetchOrderSubscriptionTokenLowLevel(orderId);\n      if (cancelled) return;\n\n      sub = await subscribe(\n        {\n          channel: token.channel,\n          topics: [...token.topics],\n          key: token.key,\n          apiBaseUrl: token.apiBaseUrl,\n        },\n        (message) => {\n          if (cancelled) return;\n          setMessages((prev) => [...prev, message.data]);\n        },\n      );\n    })();\n\n    return () => {\n      cancelled = true;\n      sub?.close?.('unmount');\n    };\n  }, [orderId]);\n\n  // ... render ...\n}\n```\n\n### SSE streaming from a route handler\n\nSubscribe inside a Next.js API route and pipe the stream to the client via SSE:\n\n```typescript\n// src/app/api/orders/[orderId]/stream/route.ts\nimport { inngest } from '@/inngest/client';\nimport { subscribe } from 'inngest/realtime';\nimport { orderChannel } from '@/inngest/channels';\n\nexport async function GET(req: Request, { params }: { params: { orderId: string } }) {\n  // ⚠ AUTHORIZATION GATE: same rule as the server-action token mint above.\n  // Authenticate the request and confirm the caller owns params.orderId\n  // before opening the SSE stream. Skipping this leaks every order's\n  // step events to anyone with a URL.\n\n  const stream = await subscribe({\n    app: inngest,\n    channel: orderChannel(params.orderId),\n    topics: ['step'],\n  });\n\n  return new Response(stream.getEncodedStream(), {\n    headers: {\n      'Content-Type': 'text/event-stream',\n      'Cache-Control': 'no-cache',\n      Connection: 'keep-alive',\n    },\n  });\n}\n```\n\nClient consumes via `fetch().getReader()` rather than the `subscribe()` callback. Use this when you want the SSE behavior or when the client-side `subscribe()` API doesn't fit your component lifecycle.\n\n## Pattern: Human-in-the-loop\n\nCombine `step.realtime.publish` with `step.waitForEvent`:\n\n```typescript\nimport crypto from 'crypto';\n\nexport const reviewWorkflow = inngest.createFunction(\n  { id: 'review-workflow', triggers: [{ event: 'review/start' }] },\n  async ({ event, step }) => {\n    const confirmationId = await step.run('gen-id', () => crypto.randomUUID());\n\n    // Publish a prompt — the client subscribes and renders an approval UI\n    await step.realtime.publish(\n      'publish-prompt',\n      reviewChannel.message,\n      { message: 'Confirm to proceed?', confirmationId },\n    );\n\n    // Wait up to 15 minutes for the user to send the matching event back\n    const confirmation = await step.waitForEvent('await-confirmation', {\n      event: 'review/confirmation',\n      timeout: '15m',\n      if: `async.data.confirmationId == \"${confirmationId}\"`,\n    });\n\n    if (!confirmation) {\n      // user didn't respond — abort or escalate\n      return { decision: 'timed_out' };\n    }\n    // continue workflow...\n  },\n);\n```\n\nThe `confirmationId` links the published prompt to the matching reply, so the workflow knows which response to act on.\n\n## Common pitfalls\n\n### Don't use `@inngest/realtime` on v4\n\nThe standalone `@inngest/realtime` package is for Inngest v3 only. On v4, all realtime APIs are in the SDK subpath `inngest/realtime`. Mixing them produces:\n- `TypeError: Cls is not a constructor` on `PUT /api/inngest` (v3 middleware class signature mismatch)\n- 401 Unauthorized on subscription tokens\n- TypeScript errors casting middleware\n\n**Verify with:** `grep '\"inngest\"' package.json` — if it's `^4.x`, use `inngest/realtime`. Period.\n\n### Don't return ChannelInstance from a Next.js server action (manual subscribe path only)\n\n`getSubscriptionToken` returns `{ channel: ChannelInstance, ... }` where ChannelInstance has zod schema methods (a class). Next.js refuses to serialize classes across the server-action → client-component boundary. Strip to primitives before returning. See \"Pattern: Manual subscribe\" above.\n\nThis gotcha does **not** apply when you use `getClientSubscriptionToken` from `inngest/react` (Step 3 — the recommended path). That helper returns a serialization-safe shape directly.\n\n### `INNGEST_DEV=1` is required for local dev\n\nWithout it, the SDK assumes cloud mode and demands `INNGEST_SIGNING_KEY` + `INNGEST_EVENT_KEY`. All realtime operations 401 / 500. Add to `.env.local`. Hard restart the dev server (Next.js does not hot-reload `.env.local` changes).\n\n### Channel topic schemas validate on publish, not on consume\n\nIf your published payload doesn't match the zod schema, the publish fails server-side. Subscriber receives nothing. Catch publish errors during step execution, or run with `validate: false` in `subscribe()` if you have a reason to skip schema validation client-side.\n\n## Reference\n\n- v4 entry points:\n  - `import { channel } from 'inngest/realtime'` — channel definitions\n  - `import { useRealtime, getClientSubscriptionToken } from 'inngest/react'` — React hook + matching token helper (Step 3 + Step 4)\n  - `import { getSubscriptionToken, subscribe } from 'inngest/realtime'` — lower-level helpers for non-React or custom transport\n- Publish methods:\n  - **Outside a step:** `step.realtime.publish(id, topicRef, data)` — wraps in a durable step\n  - **Inside `step.run`:** `inngest.realtime.publish(topicRef, data)` — already inside a memoized step, no wrapping needed\n  - **Outside a function:** `inngest.realtime.publish(topicRef, data)` — allowed but not retry-safe\n- Subscribe overloads: `subscribe(token)` returns a stream; `subscribe(token, callback)` invokes callback per message\n- Next.js Server Action gotcha (manual path only): strip `ChannelInstance` → return `{ channel: string, topics, key, apiBaseUrl }`. Not needed with `getClientSubscriptionToken`.","tags":["inngest","realtime","skills","agent-skill-repository","agent-skills","agentic-skills","ai-agents","claude-code-skills","cursor-skills","openclaw-skills"],"capabilities":["skill","source-inngest","skill-inngest-realtime","topic-agent-skill-repository","topic-agent-skills","topic-agentic-skills","topic-ai-agents","topic-claude-code-skills","topic-cursor-skills","topic-openclaw-skills"],"categories":["inngest-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/inngest/inngest-skills/inngest-realtime","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add inngest/inngest-skills","source_repo":"https://github.com/inngest/inngest-skills","install_from":"skills.sh"}},"qualityScore":"0.461","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 22 github stars · SKILL.md body (17,899 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:05:31.464Z","embedding":null,"createdAt":"2026-05-06T19:06:06.135Z","updatedAt":"2026-05-18T19:05:31.464Z","lastSeenAt":"2026-05-18T19:05:31.464Z","tsv":"'/actions':1177,1499 '/actions.ts':1004,1383 '/api/inngest':189,1866 '/app/orders':1175,1497 '/channels':771 '/client':766 '/inngest/channels':1018,1171,1397,1594 '/inngest/client':1014,1393,1586 '/llms.txt)':108 '/stream/route.ts':1582 '0':1247 '1':226,352,434,1970 '15':1768 '15m':1789 '2':377,580 '3':407,781,964,1409,1955,2086 '4':153,205,993,1105,1131,1889,2088 '401':190,1872,1994 '401s':242 '500':1995 '90':724 'abort':1799 'access':554,564,577 'across':117,1450,1924 'act':1825 'action':410,970,978,1370,1454,1613,1902,1928,2160 'add':1996 'address':1040 'admin':334,341,515,530,551,836 'adminchannel':527,769 'adminchannel.order':556,839 'agent':26,290 'ai':25,289 'aliv':1673 'allow':711,2138 'alreadi':395,677,855,2124 'anim':21,274 'anyon':1048,1640 'api':427,1367,1568,1699,1848 'apibaseurl':1476,1540,2172 'app':973,1648 'append':318 'appli':116,1115,1947 'approv':48,326,1752 'architectur':349 'arg':951 'assum':1980 'async':785,804,867,930,1020,1399,1522,1596,1732 'async.data.confirmationid':1791 'authent':1617 'author':1025,1404,1605 'autocloseontermin':1276 'await':818,832,845,862,870,886,907,910,917,925,931,1061,1071,1416,1525,1532,1646,1737,1754,1781,1784 'await-confirm':1783 'back':1778 'background':1357 'bandwidth':1275 'bare':691 'batch':1248 'behavior':1691 'bi':83 'bi-direct':82 'block':747 'boundari':1458,1932 'broadcast':72 'browser':34,305 'bufferinterv':1246 'build':81 'cach':1665,1669 'cache-control':1664 'call':379,569,598,640,697 'callback':668,1683,2153,2155 'caller':1623 'cancel':1513,1529,1544,1551 'cap':1297 'captur':848,865,893,913 'capture-pay':847,864,892,912 'case':1339 'cast':1879 'catch':2040 'chang':2011 'channel':68,283,300,315,327,342,353,375,437,438,464,474,486,490,516,528,547,553,561,571,1038,1087,1189,1294,1419,1433,1464,1534,1650,1909,2012,2070,2073,2168 'channelinst':1109,1427,1434,1436,1897,1910,1912,2166 'chat':295 'check':1047 'chunk':302 'class':443,1442,1449,1869,1918,1923 'cleanup':1159 'cli':254 'client':287,370,416,692,718,997,1163,1456,1479,1484,1576,1674,1696,1747,1930,2063 'client-compon':1455,1929 'client-sid':1695,2062 'close':1517,1554 'cloud':238,1981 'cls':181,1859 'code':637,731,743 'combin':1712 'common':613,1827 'compat':150 'complet':24,279,505,542,809,915,936,1282 'compon':1457,1704,1931 'concept':115 'confirm':1621,1761,1780,1785,1794 'confirmationid':1736,1764,1792,1809 'connect':1670 'connectionstatus':1186,1206 'const':488,526,773,788,802,815,860,868,923,1059,1069,1184,1195,1414,1473,1506,1523,1644,1722,1735,1779 'constructor':185,1863 'consum':432,1106,1139,1675,2020 'contain':1437 'content':1661 'content-typ':1660 'continu':1806 'contract':357 'control':1666 'convers':299 'core':114 'cover':1336 'creat':684,901 'critic':120,1424 'cross':522 'cross-cut':521 'crypto':1718,1720 'crypto.randomuuid':1742 'current':1029 'custom':1331,1350,1359,2103 'customeremail':790 'cut':523 'dashboard':335 'data':441,644,674,710,2113,2123,2137 'date.now':817,905 'db.order.findunique':1072 'decis':1803 'dedupl':656 'def':572 'default':163,1229 'defin':435,454 'definit':354,2074 'delay':1235 'demand':237,1984 'determin':602 'dev':225,248,256,1969,1975,2002 'develop':232 'didn':1796 'direct':84,962,1107,1368,1967 'disconnect':1278 'document':105 'doesn':1256,1700,2025 'duplic':722 'durabl':7,61,276,386,607,655,800,2117 'e.g':1243 'earlier':941 'emit':803,821,835,846,911,918,932 'emit-admin':834 'emit-ord':820 'enabl':1233,1244 'entri':2067 'env.local':229,1998,2010 'era':145 'error':1067,1082,1187,1200,1202,1878,2042 'error.message':1203 'escal':1801 'etc':707 'event':783,786,1638,1730,1733,1777,1786,1989 'event.data':792 'everi':187,1634 'exampl':755 'execut':76,2045 'export':487,525,772,1019,1178,1398,1500,1595,1721 'fail':506,543,810,2033 'fals':1264,1514,2050 'fan':344,518,1292 'fan-out':343,517,1291 'fast':1252 'fetch':1677 'fetchordersubscriptiontoken':1022,1173,1197 'fetchordersubscriptiontokenlowlevel':1401,1495,1526 'fit':1702 'flow':49,90 'focus':94 'forbidden':1083 'fulfil':477,778 'fulfill-ord':476,777 'fulfillord':774 'full':1313 'function':31,63,348,585,701,760,1021,1179,1400,1501,1597,2134 'gate':1026,1405,1606 'gen':1740 'gen-id':1739 'get':360,616,1598 'getclientsubscriptiontoken':1008,1085,1092,1951,2077,2176 'getread':1678 'getserversess':1062 'getsubscriptiontoken':1121,1387,1417,1431,1907,2090 'global':340,514 'go':100 'gotcha':1113,1944,2161 'grep':1883 'guess':1057 'guidanc':113 'handl':721,1149 'handler':636,730,954,1563 'happen':79 'hard':1999 'header':1659 'helper':1379,1960,2084,2097 'hierarchi':444 'histori':1314 'historylimit':1295 'hook':420,990,1102,1136,1145,1227,1306,1335,2081 'hot':2008 'hot-reload':2007 'human':44,86,322,1708 'human-in-the-loop':43,85,321,1707 'id':560,562,567,575,642,658,776,1042,1058,1074,1242,1725,1741,2111 'import':170,458,463,467,763,767,1007,1011,1015,1164,1168,1172,1386,1390,1394,1485,1490,1494,1583,1587,1591,1717,2069,2075,2089 'incompat':195 'inject':948 'inngest':2,56,62,104,128,152,198,204,211,217,221,224,247,253,586,764,1012,1086,1391,1418,1584,1649,1841,1884,1968,1985,1988 'inngest-c':252 'inngest-realtim':1 'inngest-setup':220 'inngest.createfunction':775,1724 'inngest.realtime':961 'inngest.realtime.publish':389,672,708,754,854,887,2121,2135 'inngest/react':1010,1094,1147,1167,1953,2079 'inngest/realtime':135,138,167,466,944,1389,1493,1590,1832,1837,1854,1892,2072,2093 'insid':390,396,583,663,666,678,687,744,851,1565,2119,2125 'instal':166,214,216 'intent':869,900 'intent-cr':899 'intent.id':903,909 'intentid':902 'inventori':921,924,929,935,937 'invok':2154 'isn':1271 'job':41,313 'keep':1286,1672 'keep-al':1671 'key':240,1211,1474,1538,1987,1990,2171 'know':1821 'languag':111,119 'language-specif':110 'last':1322 'latest':255,1309 'leak':1633 'let':1512,1515 'level':425,635,1120,1365,1378,2096 'li':1210 'lifecycl':1152,1352,1705 'lineitem':791 'link':1810 'list':339 'live':16,65,130,337,362,985 'local':231,246,1974 'log':35,306,316 'long':39,311 'long-run':38,310 'loop':47,89,325,1711 'lower':424,1119,1364,1377,2095 'lower-level':423,1118,1363,1376,2094 'm':1208 'm.data':1213,1218 'mani':1299 'manual':1122,1127,1325,1478,1903,1940,2162 'match':1776,1816,2027,2082 'matter':600 'memoiz':398,680,858,2127 'messag':1185,1262,1300,1507,1542,1760,2157 'message.data':1549 'messages.all':1304,1312 'messages.all.map':1207 'messages.bytopic':1308 'messages.delta':1319 'messages.last':1315 'method':403,621,1440,1916,2106 'mid':877 'mid-step':876 'middlewar':199,593,940,1868,1880 'mint':411,965,981,1035,1371,1413,1615 'minut':1769 'mismatch':1871 'mix':1855 'mode':1982 'modul':365 'must':720 'name':376,491,500,529,548,550,559,805,823,828,837,842,891,1215,1217,1467 'narrow':1155 'nativ':591 'need':460,885,1111,1348,2131,2174 'new':197,1066,1081,1320,1656 'next.js':972,1445,1567,1900,1919,2004,2158 'no-cach':1667 'non':430,1328,2100 'non-react':429,1327,2099 'noth':2039 'npm':139,162,215 'npx':251 'one':703,759 'one-off':702 'open':1289,1627 'oper':1993 'option':257,511,1224,1228 'order':17,271,338,478,494,532,779,822,1053,1070,1077,1635 'orderchannel':489,566,768,825,888,1016,1088,1169,1190,1395,1420,1465,1592,1651 'orderid':492,495,535,789,826,840,889,1003,1023,1033,1075,1089,1176,1181,1182,1191,1198,1382,1402,1421,1466,1498,1503,1504,1527,1556,1581,1603 'orderstatuscli':1180 'orderstatusmanu':1502 'output':507,811,830,897 'outsid':630,699,793,2107,2132 'overload':2145 'own':1031,1624 'ownership':1046,1411 'packag':124,140,146,177,1838 'package.json':202,1885 'page':19,273 'param':1601,1602 'parametr':563 'params.orderid':1625,1652 'partial':874 'pass':669 'path':1124,1905,1958,2163 'pattern':270,757,1126,1324,1706,1939 'paus':1265 'pauseonhidden':1263 'payload':2024 'payment':849,861,866,894,914,916 'per':281,285,298,472,1156,1261,1310,2156 'per-convers':297 'per-run':280,471 'period':1893 'piec':351 'pipe':1571 'pitfal':1828 'place':614 'point':2068 'prerequisit':210 'prev':1547,1548 'primit':1462,1935 'problem':268 'proceed':1763 'produc':1857 'progress':73 'project':157 'prompt':53,330,1745,1758,1813 'protocol':1360 'prototyp':1444 'publish':51,284,301,329,361,378,387,402,480,581,599,605,620,647,693,736,752,950,1743,1757,1812,2017,2023,2032,2041,2105 'publish-prompt':1756 'pure':440 'put':188,956,1865 'python':98 'rather':1679 're':394,676,728,741,1259,1342 're-rend':1258 'reach':734,750 'react':415,431,989,1255,1329,1338,1345,1487,1489,2080,2101 'react.useeffect':1511 'react.usestate':1509 'react/next.js':1141 'read':208 'real':14 'realtim':3,57,126,267,589,617,1847,1992 'realtimemiddlewar':946 'reason':1518,2057 'receiv':719,2038 'recent':1317 'recommend':450,1138,1957 'reconnect':1153 'record':812 'ref':558 'refer':101,372,2065 'refus':1446,1920 'reload':2009 'render':74,1260,1323,1557,1750 'repli':1817 'req':1599 'request':245,1600,1619 'requir':448,594,1972 'reserv':920,928,934 'reserve-inventori':919,927,933 'respond':1798 'respons':333,1430,1657,1823 'restart':2000 'retain':1302 'retri':661,715,780,2142 'retry-saf':660,714,2141 'return':906,1084,1095,1201,1204,1307,1432,1460,1463,1530,1545,1550,1655,1802,1896,1908,1937,1961,2148,2167 'review':1727 'review-workflow':1726 'review/confirmation':1787 'review/start':1731 'reviewchannel.message':1759 'reviewworkflow':1723 'right':696 'rout':705,1562,1569 'router':974 'rule':725,1608 'run':40,250,282,312,473,479,504,541,808,850,896,922,1281,2047 'runid':1245 'runtim':447 'safe':662,716,1965,2143 'safeti':453 'save':1274 'schema':260,498,533,1439,1915,2014,2030,2060 'script':706 'sdk':133,213,236,1852,1979 'secur':980 'see':218,400,1125,1938 'select':125 'send':1774 'serial':1448,1922,1964 'serialization-saf':1963 'server':249,368,409,969,977,1006,1354,1369,1385,1453,1612,1901,1927,2003,2035,2159 'server-act':1452,1611,1926 'server-sid':1353,2034 'session':1060,1064 'session.userid':1079 'set':227 'setmessag':1508,1546 'setup':222 'shape':269,549,1098,1966 'share':364 'ship':588 'short':984 'short-liv':983 'show':203 'side':1355,1697,2036,2064 'sign':239,1986 'signatur':1870 'sinc':1321 'singl':314 'skill':92,223 'skill-inngest-realtime' 'skip':1631,2059 'source-inngest' 'specif':112 'src/app/api/orders':1580 'src/app/orders':1002,1381 'src/components/orderstatusclient.tsx':1161 'src/components/orderstatusmanual.tsx':1482 'src/inngest/channels.ts':462 'src/inngest/functions/fulfill-order.ts':762 'sse':1558,1578,1629,1690 'stage':898 'standalon':137,1836 'static':552 'status':18,272,502,539,807,824,829,838,843,895,1205,1220,1222 'step':23,278,286,382,399,433,481,497,537,568,579,632,651,665,681,686,689,787,827,841,859,878,881,883,890,963,992,1091,1104,1130,1193,1408,1423,1471,1637,1654,1734,1954,2044,2085,2087,2109,2118,2128 'step-in-step':880 'step.realtime':959 'step.realtime.publish':380,641,682,738,797,819,833,1713,1755,2110 'step.run':391,639,671,746,795,852,863,926,1738,2120 'step.waitforevent':1715,1782 'store/order.placed':784 'stream':6,28,58,291,303,872,1055,1253,1267,1288,1356,1559,1573,1630,1645,2150 'stream.getencodedstream':1658 'string':493,806,813,1024,1183,1216,1221,1403,1469,1505,1519,1604,2169 'strip':1110,1425,1933,2165 'stripe.paymentintents.confirm':908 'stripe.paymentintents.create':871 'sub':1516,1531,1553 'subpath':134,1853 'subscrib':288,408,426,1000,1050,1123,1128,1132,1326,1366,1491,1533,1564,1588,1647,1682,1698,1748,1904,1941,2037,2052,2091,2144,2146,2151 'subscript':192,413,967,1151,1237,1351,1480,1875 'support':590 'symptom':172 'tab':1270 'tail':36,307 'text/event-stream':1663 'three':350 'throw':1065,1080 'time':15,1804 'timeout':1788 'token':27,193,244,292,414,968,986,996,1037,1097,1196,1373,1415,1524,1614,1876,2083,2147,2152 'token.apibaseurl':1477,1541 'token.channel':1535 'token.key':1475,1539 'token.topics':1537 'top':634 'top-level':633 'topic':70,263,317,496,531,557,578,1090,1157,1192,1311,1422,1470,1536,1653,2013,2170 'topic-agent-skill-repository' 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agents' 'topic-claude-code-skills' 'topic-cursor-skills' 'topic-openclaw-skills' 'topicref':643,673,709,2112,2122,2136 'transport':1332,2104 'trigger':782,1729 'true':1234,1277,1552 'ts':512,544,816,831,844,904 'turn':1283 'two':546 'type':194,356,452,1154,1662 'typeerror':180,1858 'typescript':96,461,761,1001,1160,1380,1481,1579,1716,1877 'ui':12,66,296,320,1753 'unauthent':1068 'unauthor':1873 'unbound':1296 'undefin':1521 'unknown':814,1510 'unmount':1555 'updat':9,59,482,875,1249 'url':1643 'use':4,67,168,174,266,405,417,623,627,737,753,796,853,943,975,1005,1162,1223,1230,1344,1361,1384,1483,1684,1831,1891,1950 'user':1030,1772,1795 'userealtim':419,1101,1135,1144,1165,1188,1334,2076 'userid':1078 'v3':121,144,1842,1867 'v3-era':143 'v4':123,129,160,179,212,587,955,1834,1845,2066 'valid':261,2015,2049,2061 'verifi':200,1027,1410,1881 'version':942 'via':1577,1676 'visibl':524,1273 'void':1520 'vs':122 'wai':55 'wait':331,1765 'waitforev':328 'want':1688 'whether':603 'without':233,994,1044,1976 'worker':1358 'workflow':8,75,277,1728,1807,1820 'would':683 'wrap':384,645,884,2114,2130 'wrapper':801 'write':729,742 'wrong':176,618 'www.inngest.com':107 'www.inngest.com/llms.txt)':106 'x':154,206,1890 'z':468 'z.enum':503,540 'z.number':513,545 'z.object':499,534 'z.record':508 'z.string':501,509,536,538 'z.unknown':510 'zod':258,446,470,1438,1914,2029","prices":[{"id":"fac39599-2afa-4026-8337-e1c9931ec36e","listingId":"a9299fad-cd5e-4d42-9514-81006ced73ea","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"inngest","category":"inngest-skills","install_from":"skills.sh"},"createdAt":"2026-05-06T19:06:06.135Z"}],"sources":[{"listingId":"a9299fad-cd5e-4d42-9514-81006ced73ea","source":"github","sourceId":"inngest/inngest-skills/inngest-realtime","sourceUrl":"https://github.com/inngest/inngest-skills/tree/main/skills/inngest-realtime","isPrimary":false,"firstSeenAt":"2026-05-06T19:06:06.135Z","lastSeenAt":"2026-05-18T19:05:31.464Z"}],"details":{"listingId":"a9299fad-cd5e-4d42-9514-81006ced73ea","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"inngest","slug":"inngest-realtime","github":{"repo":"inngest/inngest-skills","stars":22,"topics":["agent-skill-repository","agent-skills","agentic-skills","ai-agents","claude-code-skills","cursor-skills","openclaw-skills"],"license":"other","html_url":"https://github.com/inngest/inngest-skills","pushed_at":"2026-05-06T18:21:48Z","description":"Agent Skills for building with Inngest","skill_md_sha":"fdcfa2862e2de0e334e86b4abe222acec318b1bf","skill_md_path":"skills/inngest-realtime/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/inngest/inngest-skills/tree/main/skills/inngest-realtime"},"layout":"multi","source":"github","category":"inngest-skills","frontmatter":{"name":"inngest-realtime","description":"Use when streaming durable workflow updates to a UI in real time — live order status pages that animate as steps complete, AI agent token streaming from a function to the browser, log tailing for long-running jobs, or human-in-the-loop approval flows that publish a prompt and wait for a user reply. Covers Inngest v4 native realtime: defining typed channels, publishing from inside step.run, minting subscription tokens via server actions, and consuming the stream from React/Next.js client components."},"skills_sh_url":"https://skills.sh/inngest/inngest-skills/inngest-realtime"},"updatedAt":"2026-05-18T19:05:31.464Z"}}