Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/workflows/sync-webhook-events.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Sync Platform SDK webhook events

# Adds the webhook event types production publishes to packages/platform/scripts/webhook-event-types.json
# and regenerates every type from the published schemas, so it picks up a new event and a changed
# schema alike. Changes go to one rolling PR; closing it
# without merging only skips that run, since the next one proposes the same changes again.
# The sibling of sync-asyncapi.yml, which does the same for the builder socket events.

on:
schedule:
- cron: "45 * * * *" # Every hour, apart from sync-asyncapi's :15
workflow_dispatch:

permissions:
contents: write
pull-requests: write

concurrency:
group: sync-webhook-events
cancel-in-progress: false

env:
BRANCH: bot/platform-webhook-events-sync
WEBHOOKS_OPENAPI_URL: https://app.base44.com/api/v1/outbound-webhooks/openapi.json
EVENT_LIST: packages/platform/scripts/webhook-event-types.json
GENERATED: packages/platform/src/server/modules/webhooks.events.generated.ts

jobs:
sync:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: main

- name: Wix gateway proxy (mandatory)
uses: ./.github/actions/wix-gateway-proxy

- name: Use Node.js 20.x
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "20.x"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Sync the event list and regenerate the types
run: |
npm run sync:webhooks -w @base44/platform -- "$WEBHOOKS_OPENAPI_URL"
npm run generate:webhooks -w @base44/platform

- name: Check for changes
id: changes
run: |
if git diff --quiet -- "$EVENT_LIST" "$GENERATED"; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "✅ The webhook event types are up to date"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Close a stale sync PR
if: steps.changes.outputs.changed == 'false'
env:
GH_TOKEN: ${{ github.token }}
run: |
pr=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number')
if [ -n "$pr" ]; then gh pr close "$pr" --delete-branch --comment "main already has every published event type."; fi

- name: Build the docs and test the Platform SDK
if: steps.changes.outputs.changed == 'true'
run: |
npm run create-docs -w @base44/platform
npm test -w @base44/platform

- name: Update the sync PR
if: steps.changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -B "$BRANCH"
git add "$EVENT_LIST" "$GENERATED"
git commit -m "chore(platform): sync the webhook event types from production"

pr=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number')
# An open sync PR whose branch already has this exact tree needs no new commit or force-push.
if [ -n "$pr" ] && git fetch --depth=1 origin "$BRANCH" && [ "$(git rev-parse 'HEAD^{tree}')" = "$(git rev-parse 'FETCH_HEAD^{tree}')" ]; then
echo "✅ #$pr already has these changes"
exit 0
fi
# Rebuilt from main every run, so the branch never drifts or conflicts.
git push --force origin "$BRANCH"

body="Adds any new webhook event types published at $WEBHOOKS_OPENAPI_URL to \`webhook-event-types.json\` and regenerates \`webhooks.events.generated.ts\` from https://base44.com/webhooks/schemas, which also picks up changed schemas. The docs build and the Platform SDK tests passed on this change. Checked against production every hour."
if [ -z "$pr" ]; then
gh pr create --head "$BRANCH" --base main --title "Sync Platform SDK webhook events from production" --body "$body"
fi
3 changes: 2 additions & 1 deletion packages/platform/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"copy-docs-local": "node ../sdk/scripts/mintlify-post-processing/copy-to-local-docs.js --package-dir .",
"create-docs:generate": "node -e \"fs.rmSync('docs',{recursive:true,force:true})\" && typedoc --options typedoc.mintlify.json",
"create-docs:process": "node ../sdk/scripts/mintlify-post-processing/file-processing/file-processing.js --package-dir .",
"generate:webhooks": "node scripts/generate-webhook-types.mjs"
"generate:webhooks": "node scripts/generate-webhook-types.mjs",
"sync:webhooks": "node scripts/sync-webhook-event-types.mjs"
},
"dependencies": {
"socket.io-client": "^4.8.3"
Expand Down
22 changes: 22 additions & 0 deletions packages/platform/scripts/sync-webhook-event-types.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env node
// Adds the webhook event types production publishes to scripts/webhook-event-types.json:
// npm run sync:webhooks [-- <openapi url>]
// Then run `npm run generate:webhooks`. The hourly sync-webhook-events workflow does both.
//
// Only adds. A retired type leaves the published spec, but endpoints that selected it keep
// receiving it, so the SDK keeps its type.
import { readFile, writeFile } from "node:fs/promises";

const url =
process.argv[2] ?? process.env.WEBHOOKS_OPENAPI_URL ?? "https://app.base44.com/api/v1/outbound-webhooks/openapi.json";
const target = new URL("./webhook-event-types.json", import.meta.url);

const response = await fetch(url);
if (!response.ok) throw new Error(`GET ${url} returned ${response.status}`);
const published = Object.keys((await response.json()).webhooks ?? {});
if (!published.length) throw new Error(`${url} has no webhooks section. Kept the committed list.`);

const known = JSON.parse(await readFile(target, "utf8"));
const added = published.filter(type => !known.includes(type));
await writeFile(target, JSON.stringify([...known, ...added], null, 2) + "\n");
console.log(added.length ? `Added ${added.join(", ")} from ${url}.` : `No new event types at ${url}.`);
7 changes: 4 additions & 3 deletions packages/platform/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ export { WebhookConfigurationError, WebhookVerificationError } from "./errors.js
export type { WebhookConfigurationErrorCode, WebhookVerificationErrorCode } from "./errors.types.js";
export type { PlatformServer } from "./server.types.js";
export type {
AppCreatedV1Data, AppDeletedV1Data, AppPublishedV1Data, AppRestoredV1Data, AppUnpublishedV1Data, CloudEvent, CreateHandlerOptions,
DeliveredWebhookEvent, UnknownWebhookEvent, VerifyEventOptions, WebhookEventHandlers, WebhookEvent, WebhookEventMap, WebhookEventType, WebhookHeaders,
WebhookPublicKeys, WebhooksModule, WebhookTestV1Data,
CloudEvent, CreateHandlerOptions, DeliveredWebhookEvent, UnknownWebhookEvent, VerifyEventOptions, WebhookEventHandlers, WebhookEvent,
WebhookEventType, WebhookHeaders, WebhookPublicKeys, WebhooksModule,
} from "./modules/webhooks.types.js";
// Every event's data type, so a type the sync adds is exported and documented without an edit here.
export type * from "./modules/webhooks.events.generated.js";
9 changes: 5 additions & 4 deletions packages/platform/tests/types/webhooks.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
createPlatformServer, type AppCreatedV1Data, type WebhookEvent, type WebhookEventMap, type WebhookTestV1Data,
createPlatformServer, type AppCreatedV1Data, type WebhookEvent, type WebhookEventMap, type WebhookEventType,
type WebhookTestV1Data,
} from "@base44/platform/server";

const platform = createPlatformServer();
Expand Down Expand Up @@ -27,8 +28,8 @@ async function handle(body: string, headers: Headers) {
break;
}
default: {
// A default branch still sees the event (the remaining known types), so it can be logged.
const type: "app.unpublished.v1" | "app.restored.v1" = event.type;
// A default branch still sees the event (the known types not handled above), so it can be logged.
const type: Exclude<WebhookEventType, "app.created.v1" | "app.published.v1" | "app.deleted.v1" | "webhook.test.v1"> = event.type;
void type;
}
}
Expand All @@ -45,7 +46,7 @@ platform.webhooks.createHandler({
// Examples published with the schemas must stay assignable to the generated types.
const created: AppCreatedV1Data = {
app_id: "68b0c1d2e3f4a5b6c7d8e9f0", workspace_id: "68a1b2c3d4e5f6a7b8c9d0e1", owner_id: "6899aabbccddeeff00112233",
owner_service_external_id: "sunny-9f2c4a", app_type: "user_app", created_at: "2026-09-01T10:15:30.123Z",
app_type: "user_app", created_at: "2026-09-01T10:15:30.123Z",
};
const probe: WebhookTestV1Data = { endpoint_id: "e", workspace_id: "w", sent_at: "2026-09-01T10:15:30.123Z", challenge: "8f14e45f" };
const map: Pick<WebhookEventMap, "app.created.v1" | "webhook.test.v1"> = { "app.created.v1": created, "webhook.test.v1": probe };
Expand Down
Loading