{"record":{"id":"32fd7ec16a52afbd","repo":"Budibase/budibase","slug":"webhook-schema-can-only-be-built-in-development","errorCode":null,"errorMessage":"Webhook schema can only be built in development","messagePattern":"Webhook schema can only be built in development","errorType":"http","errorClass":"HTTPError","httpStatus":400,"severity":"error","filePath":"packages/server/src/api/controllers/webhook.ts","lineNumber":72,"sourceCode":"  ctx.body = {\n    message: \"Webhook created successfully\",\n    webhook,\n  }\n}\n\nexport async function destroy(ctx: Ctx<void, DeleteWebhookResponse>) {\n  ctx.body = await sdk.automations.webhook.destroy(\n    ctx.params.id,\n    ctx.params.rev\n  )\n}\n\nconst assertSchemaWorkspace = (instance: string) => {\n  if (dbCore.isDevWorkspaceID(instance)) {\n    return\n  }\n\n  throw new HTTPError(\"Webhook schema can only be built in development\", 400)\n}\n\nconst updateWebhookSchema = async (\n  body: BuildWebhookSchemaRequest,\n  webhook: Webhook,\n  db: ReturnType<typeof context.getWorkspaceDB>\n) => {\n  webhook.bodySchema = toJsonSchema(body)\n  if (webhook.action.type === WebhookActionType.AUTOMATION) {\n    const automation = await db.tryGet<Automation>(webhook.action.target)\n    if (!automation) {\n      throw new HTTPError(\"Automation not found\", 404)\n    }\n    const autoOutputs = automation.definition.trigger.schema.outputs\n    const properties = webhook.bodySchema?.properties\n    autoOutputs.properties = {\n      body: autoOutputs.properties.body,\n    }","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/webhook.ts#L54-L90","documentation":"Budibase webhooks define their request body schema against a development copy of the workspace. When the webhook schema build endpoint is called for a workspace whose ID is not a development workspace ID, assertSchemaWorkspace rejects it with HTTP 400. This prevents mutating published/prod app definitions from webhook schema sync.","triggerScenarios":"Calling buildSchema (POST webhook schema build) with ctx.params.instance set to a published/prod (non-dev) workspace ID.","commonSituations":"Pointing an external service at a prod app's webhook schema-build URL; copying a webhook ID from a dev app and using it against the deployed app; automated tooling that iterates all workspaces without filtering dev ones.","solutions":["Run the schema build against the development workspace ID for the app (the appid ending in _dev).","If the change must reach prod, redeploy/publish the app after building the schema in dev.","Check that the instance/appId parameter in the request URL is the dev app ID, not the prod one.","If automation-driven, update the automation config to use the dev app ID for schema sync operations."],"exampleFix":"// before\nawait api.post(`/api/webhooks/schema/${prodAppId}/${webhookId}`)\n// after\nawait api.post(`/api/webhooks/schema/${devAppId}/${webhookId}`) // appId ending in _dev","handlingStrategy":"validation","validationCode":"import { isDevWorkspaceID } from \"@budibase/backend-core/db\";\nif (!isDevWorkspaceID(appId)) {\n  throw new Error(\"Schema build must target the dev workspace (appId ending in _dev)\")\n}","typeGuard":"const isDev = (appId: string): boolean => isDevWorkspaceID(appId)","tryCatchPattern":"try {\n  await buildWebhookSchema(appId, webhookId)\n} catch (e) {\n  if (e.status === 400 && /development/.test(e.message)) {\n    appId = toDevAppId(appId); return retry(appId, webhookId)\n  }\n  throw e\n}","preventionTips":["Always store and use the dev app ID for schema-sync operations.","Publish to prod via deploy rather than mutating prod docs directly.","Centralize webhook schema build calls behind a helper that asserts dev app IDs.","Log the app ID used so mismatches are obvious in failures."],"tags":["webhook","http-400","workspace","development-only"],"backgroundTag":"operation-not-allowed-in-environment","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}