Next.js quickstart
Ship a working customer-facing schedule in a Next.js App Router project. Connect the trusted browser proxy and signed webhook, register one action, and render the scheduling UI; Approvals, Bulk, and Audit reuse the same integration.
Before you start
| You need | Where it comes from | Why |
|---|---|---|
| Project secret key | Dashboard → project API keys | Authenticates server API calls. |
| Webhook signing secret | Dashboard → project delivery settings | Verifies that action deliveries came from @incld. |
| Authenticated application user | Your existing server session | Scopes browser requests and supplies trusted actor identity. |
| Schedules entitlement | Developer project or paid Schedules plan | Allows schedule creation and execution. |
This guide assumes Next.js App Router and an existing
auth()
helper. Substitute your own session library; the invariant is that identity comes from the server, never request JSON.
1. Install the packages
npm install @incld/client @incld/react @incld/react-schedules\nnpm install --save-dev tsx
Import the shared and feature styles once in your application stylesheet:
@import "@incld/react/styles.css";
@import "@incld/react-schedules/styles.css";
2. Configure server secrets
INCLD_SECRET_KEY=sk_live_replace_me
INCLD_WEBHOOK_SECRET=whsec_replace_me
INCLD_API_URL=https://api.incld.dev
Never prefix these variables with NEXT_PUBLIC_
3. Create the server integration
import "server-only"
import { createIncld, defineActions } from "@incld/client/next"
import { auth } from "@/lib/auth"
import { reports } from "@/lib/reports"
const actions = defineActions({
generate_report: {
displayName: "Generate report",
payloadSchema: {
type: "object",
properties: { accountId: { type: "string" } },
required: ["accountId"],
},
async run({ payload, event }) {
await reports.generate(payload.accountId, {
idempotencyKey: event.idempotencyKey,
})
},
},
})
export const incld = createIncld({
apiKey: process.env.INCLD_SECRET_KEY!,
webhookSecret: process.env.INCLD_WEBHOOK_SECRET!,
baseUrl: process.env.INCLD_API_URL,
actions,
async resolveContext() {
const session = await auth()
if (!session?.user.id || !session.organizationId) return null
return {
user: { id: session.user.id },
organization: { id: session.organizationId },
roles: session.user.roles,
permissions: session.user.permissions,
}
},
async authorize({ context, operation }) {
return context.permissions?.includes(`incld:${operation}`) === true
},
})
4. Mount separate proxy and webhook routes
import { incld } from "@/lib/incld"
export const dynamic = "force-dynamic"
export const { GET, POST, PATCH, DELETE } = incld.routes
import { incld } from "@/lib/incld"
export const dynamic = "force-dynamic"
export const POST = incld.webhook
The provider base URL is /api/incld; the browser client appends /v1, which is why the catch-all route lives below /api/incld/v1. The browser route authenticates your user. The webhook route authenticates @incld. Do not combine them.
5. Add the shared React provider
"use client"
import { IncldProvider, type IncldProviderProps } from "@incld/react"
export function Providers({ children }: { children: IncldProviderProps["children"] }) {
return (
<IncldProvider
baseUrl="/api/incld"
appearance={{ colorScheme: "system", accentColor: "indigo" }}
onError={(error) => console.error(error.code, error.requestId)}
>
{children}
</IncldProvider>
)
}
6. Render a component
"use client"
import { ScheduleList, ScheduleTrigger } from "@incld/react-schedules"
export function ReportAutomation() {
return (
<section>
<ScheduleTrigger
action="generate_report"
defaultPayload={{ accountId: "acct_42" }}
>
Schedule report
</ScheduleTrigger>
<ScheduleList filters={{ action: "generate_report" }} />
</section>
)
}
7. Sync actions
import { incld } from "../src/lib/incld"
await incld.syncActions()
console.log("incld actions synchronized")
{
"scripts": {
"incld:sync": "tsx scripts/sync-incld.ts"
}
}
npm run incld:sync
Run this after deploying code and before users create Schedules or Bulk operations. Synchronization is an idempotent upsert, so repeating it during every deploy is safe.
8. Verify the request and delivery paths
-
01
Confirm the action registry
The dashboard should show generate_report after the sync command completes. A missing action means the server key, API URL, or sync process is wrong.
-
02
Configure delivery
Set the dashboard webhook endpoint to https://your-app.example/api/incld/webhook and copy the matching signing secret.
-
03
Exercise the browser proxy
While signed in to your app, create a schedule. The browser should call /api/incld/v1/schedules without any Authorization header or identity field supplied by your component.
-
04
Observe delivery
Trigger or wait for a run. Confirm a 2xx webhook response, one handler execution for the event idempotency key, and the resulting run status in the dashboard.
If the first request fails
| Result | Most likely cause |
|---|---|
| 401 context_required | resolveContext returned null or did not resolve a trusted user. |
| 403 organization_context_required | A tenant-bound request had no trusted organization.id. Resolve it from the server session. |
| 403 context_forbidden | authorize rejected the exact operation, such as schedules.read or schedules.create. |
| 403 component_not_enabled | Schedules is not enabled for this project. |
| 422 action_not_declared | The delivered action was not included in defineActions in the running deployment. |
| 401 signature_invalid | Wrong webhook secret, modified request body, or more than five minutes of clock skew. |
| 404 route_not_found | Provider baseUrl and the /api/incld/v1 catch-all route do not align. |