feat: Implement webhooks system for organization events#1303
Conversation
Add complete webhooks infrastructure including database schema, backend edge functions for CRUD operations and delivery, frontend UI components and stores, and comprehensive tests. Webhooks allow organizations to receive HTTP notifications for app version, channel, org member, and organization changes via audit_logs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughAdds a complete webhook subsystem: frontend UI components and Pinia store, backend REST handlers and triggers, delivery queueing/processing and utilities, DB schema/migration, e2e tests, config, and i18n strings across locales. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as Webhooks UI
participant Store as Pinia Store
participant API as Webhooks API
participant DB as Database
participant Queue as pgmq Queue
participant Dispatcher as Dispatcher Trigger
participant Worker as Delivery Processor
participant Target as Webhook Target
User->>UI: Create webhook
UI->>Store: createWebhook()
Store->>API: POST /webhooks
API->>DB: INSERT webhooks
API-->>Store: return webhook
Store-->>UI: show success
Note over DB,Dispatcher: Audit event triggers dispatcher
DB->>Dispatcher: audit_log trigger payload
Dispatcher->>DB: query enabled webhooks
Dispatcher->>DB: INSERT delivery record(s)
Dispatcher->>Queue: enqueue delivery job(s)
Queue->>Worker: dequeue job
Worker->>DB: fetch delivery & webhook
Worker->>Target: POST payload (with signature)
Target-->>Worker: response
Worker->>DB: update delivery (status, response, attempts)
alt delivery failed && attempts < max
Worker->>DB: set next_retry_at
Worker->>Queue: schedule delayed retry
else if attempts >= max
Worker->>DB: mark failed, create notification
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used📓 Path-based instructions (6)**/*.{ts,tsx,js,jsx,vue}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
supabase/functions/_backend/**📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
supabase/functions/**/*.ts📄 CodeRabbit inference engine (CLAUDE.md)
Files:
supabase/functions/_backend/**/*.{ts,js}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
supabase/functions/**/*.{ts,js}📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Files:
🧠 Learnings (9)📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-05T17:34:25.556ZApplied to files:
📚 Learning: 2025-12-23T02:53:12.055ZApplied to files:
📚 Learning: 2025-12-05T17:34:25.556ZApplied to files:
🧬 Code graph analysis (1)supabase/functions/_backend/public/webhooks/deliveries.ts (3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Fix import ordering, unused imports, prefer-exponentiation-operator, and static class attribute issues caught by eslint. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const { error } = await supabaseAdmin(c).rpc('pgmq_send' as any, { | ||
| queue_name: 'webhook_delivery', | ||
| message, | ||
| }) |
There was a problem hiding this comment.
Replace missing pgmq_send RPC with callable queue func
The delivery queueing code calls supabaseAdmin(c).rpc('pgmq_send'), but this commit only creates/uses pgmq.send and does not add a public.pgmq_send wrapper anywhere. PostgREST only exposes callable RPCs in exposed schemas, so this will return “function not found” and no webhook deliveries (including manual retries) will ever be enqueued. Add a public wrapper for pgmq.send or call an existing RPC that actually exists.
Useful? React with 👍 / 👎.
| -- ===================================================== | ||
| -- Add webhook_dispatcher and webhook_delivery to CRON processing | ||
| -- This modifies the process_all_cron_tasks function to include webhook queues | ||
| -- ===================================================== | ||
| -- Note: The actual CRON modification should be done in a separate step |
There was a problem hiding this comment.
Ensure webhook queues are scheduled in cron runner
This migration creates the webhook_dispatcher/webhook_delivery queues but never adds them to process_all_cron_tasks (the comment explicitly defers it). I checked the current process_all_cron_tasks definition and it only processes the existing queues, so these new queues will never be consumed in production and audit_log events will never be dispatched or delivered. Please add the new queue names to the appropriate process_function_queue call.
Useful? React with 👍 / 👎.
- Add public.pgmq_send wrapper function callable via PostgREST RPC - Update process_all_cron_tasks to include webhook_dispatcher and webhook_delivery queues in high-frequency processing (every 10s) - Add cleanup_webhook_deliveries call to daily midnight tasks Fixes issues raised in PR review: - pgmq_send RPC was missing (no public wrapper for pgmq.send) - Webhook queues were not scheduled in CRON processing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove the public.pgmq_send wrapper function and update webhook utils to use getPgClient with direct SQL queries to pgmq.send. This avoids exposing a public RPC function while still enabling queue operations from edge functions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use `as any` type assertions for webhooks and webhook_deliveries table operations since the Supabase types haven't been regenerated yet to include the new tables from the migration. Also fixed unused import in webhooks.test.ts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 15
♻️ Duplicate comments (1)
supabase/functions/_backend/public/webhooks/delete.ts (1)
29-47: Critical: Same TypeScript type errors as webhook.ts utility.The pipeline failures here are caused by the same missing table definitions in
supabase.types.ts. Once types are regenerated (as noted in the webhook.ts review), these errors will resolve.The delete logic itself is correct:
- Validates input
- Checks admin permissions
- Verifies webhook ownership
- Relies on CASCADE delete for deliveries (as mentioned in the AI summary)
🧹 Nitpick comments (6)
supabase/functions/_backend/public/webhooks/test.ts (1)
18-94: LGTM! Test endpoint implements proper authorization and immediate delivery.The test function correctly:
- Validates input with Zod schema
- Enforces admin-level access via
hasOrgRightApikey- Verifies webhook ownership
- Creates a test delivery record
- Delivers immediately (bypassing queue for instant feedback)
- Updates delivery results and attempt count
This provides a good testing mechanism for webhook endpoints.
Optional: Consider using the incrementAttemptCount helper
Lines 80-83 directly update the attempt_count, but the utility file exports
incrementAttemptCount. For consistency, consider:- // Update attempt count - await supabaseAdmin(c) - .from('webhook_deliveries') - .update({ attempt_count: 1 }) - .eq('id', delivery.id) + // Mark as attempted (test deliveries count as 1 attempt) + await supabaseAdmin(c) + .from('webhook_deliveries') + .update({ attempt_count: 1 }) + .eq('id', delivery.id)However, since test deliveries are a special case that directly set to 1 (not increment), the current approach is acceptable.
src/components/WebhookDeliveryLog.vue (1)
138-331: LGTM! Template uses Tailwind utilities appropriately.The template correctly:
- Uses Tailwind utility classes for layout and styling (as per guidelines)
- Implements modal with proper backdrop handling and z-index
- Provides good accessibility with keyboard interactions
- Uses unplugin-icons for heroicons (following established patterns)
- Maintains responsive design with proper breakpoints
- Implements expandable/collapsible sections with smooth interactions
No DaisyUI misuse detected (correctly uses utilities for layout; DaisyUI is reserved for interactive primitives which aren't needed here).
Optional: Consider extracting status badge component
Lines 229-234 implement a status badge that appears multiple times in the codebase. If this pattern repeats, consider extracting to a reusable component:
<!-- StatusBadge.vue --> <template> <span class="px-2 py-0.5 text-xs font-medium rounded" :class="colorClass" > {{ status }} </span> </template>However, this is purely optional as the current implementation is perfectly fine.
supabase/functions/_backend/utils/webhook.ts (1)
70-75: Consider adding a cross-reference comment to clarify the intentional architectural separation.The backend and frontend
WEBHOOK_EVENT_TYPEShave different structures by design: the backend maintains a minimal string array for validation, while the frontend enriches the data with labels and descriptions for UI rendering. Event type values already match exactly ('app_versions','channels','org_users','orgs'), so no validation inconsistency exists. Adding a comment like:// Frontend labels/descriptions are defined in src/stores/webhooks.tswould clarify this relationship and help prevent future maintenance issues if event types are added or removed.
src/components/WebhookForm.vue (1)
122-128: Consider using DaisyUI components for form inputs and buttons.Per coding guidelines, interactive elements should use DaisyUI components (
d-btn,d-input) for consistent behavior and spacing. The current implementation uses custom Tailwind classes.Also applies to: 135-142, 165-170, 189-195, 204-218
tests/webhooks.test.ts (1)
40-48: Cleanup may leave orphaned webhooks on test failure.If any test fails before
createdWebhookIdis set or after webhook creation, theafterAllcleanup will skip deleting the webhook (line 42-44 only runs ifcreatedWebhookIdis truthy). Consider cleaning up all webhooks for the test org unconditionally.🔎 Suggested cleanup improvement
afterAll(async () => { - // Clean up created webhooks - if (createdWebhookId) { - await getSupabaseClient().from('webhooks').delete().eq('id', createdWebhookId) - } + // Clean up all webhooks for test org (handles partial test runs) + await getSupabaseClient().from('webhooks').delete().eq('org_id', WEBHOOK_TEST_ORG_ID) // Clean up test organization and stripe_info await getSupabaseClient().from('orgs').delete().eq('id', WEBHOOK_TEST_ORG_ID) await getSupabaseClient().from('stripe_info').delete().eq('customer_id', customerId) })supabase/migrations/20251226133417_webhooks.sql (1)
31-51: Webhook deliveries table structure looks solid.Good design with appropriate indexes for common query patterns:
- Lookup by webhook_id
- Pagination by org_id + created_at DESC
- Pending retry queries with partial index
The
statuscolumn uses TEXT but could benefit from a CHECK constraint for valid values ('pending', 'success', 'failed').🔎 Add status constraint
"status" TEXT NOT NULL DEFAULT 'pending', -- pending, success, failed + CONSTRAINT "webhook_deliveries_status_check" CHECK ("status" IN ('pending', 'success', 'failed')),
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
messages/en.jsonsrc/components/WebhookDeliveryLog.vuesrc/components/WebhookForm.vuesrc/constants/organizationTabs.tssrc/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/put.tssupabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/triggers/index.tssupabase/functions/webhooks/index.tssupabase/migrations/20251226133417_webhooks.sqltests/webhooks.test.ts
🧰 Additional context used
📓 Path-based instructions (17)
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/public/webhooks/post.tstests/webhooks.test.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/triggers/index.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssrc/pages/settings/organization/Webhooks.vuesupabase/functions/_backend/public/webhooks/put.tssupabase/functions/_backend/utils/webhook.tssrc/stores/webhooks.tssrc/components/WebhookForm.vuesrc/components/WebhookDeliveryLog.vuesrc/constants/organizationTabs.tssupabase/functions/webhooks/index.ts
supabase/functions/_backend/**
📄 CodeRabbit inference engine (CLAUDE.md)
Backend logic should be organized in
supabase/functions/_backend/with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssupabase/functions/_backend/public/webhooks/put.tssupabase/functions/_backend/utils/webhook.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/public/webhooks/post.tstests/webhooks.test.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/triggers/index.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssupabase/functions/_backend/public/webhooks/put.tssupabase/functions/_backend/utils/webhook.tssrc/stores/webhooks.tssrc/constants/organizationTabs.tssupabase/functions/webhooks/index.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Supabase Edge Functions use Deno runtime
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/triggers/index.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssupabase/functions/_backend/public/webhooks/put.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/webhooks/index.ts
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssupabase/functions/_backend/public/webhooks/put.tssupabase/functions/_backend/utils/webhook.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/triggers/index.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssupabase/functions/_backend/public/webhooks/put.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/webhooks/index.ts
tests/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Backend tests should be located in the
tests/directory and use Vitest test runner
Files:
tests/webhooks.test.ts
tests/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend tests must use helpers from
tests/test-utils.tsincludinggetEndpointUrl(path)for correct worker routing andUSE_CLOUDFLARE_WORKERS=truefor CF Workers testing
Files:
tests/webhooks.test.ts
src/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.vue: Use Vue 3 with Composition API and<script setup>syntax for frontend components
Style components using TailwindCSS with DaisyUI components
src/**/*.vue: Use DaisyUI (d-prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent
Konsta components are reserved for the safe area helpers; avoid importingkonstaanywhere else in the app
src/**/*.vue: Use Vue 3<script setup>syntax exclusively for all Vue component scripts
Use Tailwind utility classes for layout and spacing in Vue components
Use DaisyUI components (d-btn,d-input,d-card) for interactive elements in Vue components
Use Konsta components ONLY for safe area helpers (top/bottom insets) in Vue components; avoid other uses
UseuseRoute()fromvue-routerto access route parameters anduseRouter()for programmatic navigation in Vue components
Files:
src/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vuesrc/components/WebhookDeliveryLog.vue
src/pages/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
Use file-based routing with unplugin-vue-router for frontend pages
Frontend file-based routing uses
src/pages/directory structure; routes auto-generate withunplugin-vue-routerand types are available insrc/typed-router.d.ts
Files:
src/pages/settings/organization/Webhooks.vue
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}
📄 CodeRabbit inference engine (CLAUDE.md)
Mobile apps should use Capacitor with app ID
ee.forgr.capacitor_gofor native mobile functionality
Files:
src/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.tssrc/components/WebhookForm.vuesrc/components/WebhookDeliveryLog.vuesrc/constants/organizationTabs.ts
src/**/*.{vue,css}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{vue,css}: The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Capgo's look centers on deep slate bases with the 'Extract' azure highlight (--color-azure-500: #119eff) and soft radii; mirror the palette fromsrc/styles/style.csswhen introducing new UI
Files:
src/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vuesrc/components/WebhookDeliveryLog.vue
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.tssrc/components/WebhookForm.vuesrc/components/WebhookDeliveryLog.vuesrc/constants/organizationTabs.ts
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.tssrc/components/WebhookForm.vuesrc/components/WebhookDeliveryLog.vuesrc/constants/organizationTabs.ts
src/stores/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Pinia stores for state management
Files:
src/stores/webhooks.ts
src/components/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
Reusable Vue components should be organized in
src/components/directory
Files:
src/components/WebhookForm.vuesrc/components/WebhookDeliveryLog.vue
supabase/migrations/**/*.sql
📄 CodeRabbit inference engine (AGENTS.md)
supabase/migrations/**/*.sql: When a feature requires schema changes, create a single migration file withsupabase migration new <feature_slug>and keep editing that file until the feature ships; never edit previously committed migrations
A migration that introduces a new table may include seed inserts for that table, treating that seeding as part of the current feature and not modifying previously committed migrations
Do not create new cron jobs; instead update theprocess_all_cron_tasksfunction in a new migration file to add your job if neededDatabase migrations must be created with
supabase migration new <feature_slug>and never modify previously committed migrations
Files:
supabase/migrations/20251226133417_webhooks.sql
🧠 Learnings (19)
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/public/webhooks/put.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/webhooks/index.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssupabase/functions/_backend/public/webhooks/put.tssupabase/functions/webhooks/index.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Backend code must be placed in `supabase/functions/_backend/` as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Applied to files:
supabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/public/webhooks/put.tssupabase/functions/webhooks/index.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/triggers/index.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssupabase/functions/webhooks/index.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to cloudflare_workers/api/index.ts : API Worker (port 8787) routes: `/bundle`, `/app`, `/device`, `/channel`, `/private/*`, `/triggers`
Applied to files:
supabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/triggers/index.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/triggers/webhook_dispatcher.tssupabase/functions/webhooks/index.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to supabase/functions/_backend/** : Backend logic should be organized in `supabase/functions/_backend/` with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Applied to files:
supabase/functions/_backend/public/webhooks/index.tssupabase/functions/webhooks/index.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to supabase/functions/**/*.ts : Supabase Edge Functions use Deno runtime
Applied to files:
supabase/functions/_backend/public/webhooks/index.tssupabase/functions/webhooks/index.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/**/*.vue : Use Vue 3 with Composition API and `<script setup>` syntax for frontend components
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-05T17:34:36.569Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-05T17:34:36.569Z
Learning: Applies to src/**/*.{vue,css} : The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-23T01:19:04.593Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:04.593Z
Learning: In the Cap-go codebase, ensure that app permission checks never include the role 'owner'. App-level permissions should be based on the user_min_right enum with values: read, upload, write, admin, super_admin (and NOT owner). This pattern applies across Vue components that perform permission checks; if you find a check referencing 'owner' for app-level access, replace it with the appropriate user_min_right value and keep organization-level owner handling in organization.ts.
Applied to files:
src/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vuesrc/components/WebhookDeliveryLog.vue
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use structured logging with `cloudlog({ requestId: c.get('requestId'), message: '...' })` for all backend logging
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/stores/**/*.{ts,tsx} : Use Pinia stores for state management
Applied to files:
src/stores/webhooks.ts
📚 Learning: 2025-10-30T14:58:37.007Z
Learnt from: Dalanir
Repo: Cap-go/capgo PR: 1238
File: src/layouts/settings.vue:7-13
Timestamp: 2025-10-30T14:58:37.007Z
Learning: In the Cap-go/capgo repository, heroicons are imported using the pattern `~icons/heroicons/icon-name` (e.g., `~icons/heroicons/user`, `~icons/heroicons/bell`) without requiring `/outline/` or `/solid/` path segments. Solid variants use a `-solid` suffix in the icon name itself (e.g., `~icons/heroicons/arrow-path-solid`).
Applied to files:
src/constants/organizationTabs.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend files
Applied to files:
supabase/functions/webhooks/index.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Use shared backend code from `supabase/functions/_backend/` across all deployment platforms; never create platform-specific implementations outside this directory
Applied to files:
supabase/functions/webhooks/index.ts
📚 Learning: 2025-12-05T17:34:36.569Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-05T17:34:36.569Z
Learning: Applies to supabase/migrations/**/*.sql : Do not create new cron jobs; instead update the `process_all_cron_tasks` function in a new migration file to add your job if needed
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
📚 Learning: 2025-12-05T17:34:36.569Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-05T17:34:36.569Z
Learning: Applies to supabase/migrations/**/*.sql : A migration that introduces a new table may include seed inserts for that table, treating that seeding as part of the current feature and not modifying previously committed migrations
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
📚 Learning: 2025-12-25T11:22:13.039Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:13.039Z
Learning: In SQL migrations under the repository (e.g., supabase/migrations), enforce that when an org has enforcing_2fa=true, all users (including super_admins) must have 2FA enabled before accessing any org functions, including check_org_members_2fa_enabled. Do not grant admin exceptions to 2FA requirements. This ensures consistent security enforcement across all org-related operations; implement this rule within relevant migrations and associated stored procedures/tests.
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
🧬 Code graph analysis (9)
supabase/functions/_backend/public/webhooks/test.ts (3)
supabase/functions/_backend/utils/hono.ts (1)
simpleError(247-249)supabase/functions/_backend/utils/supabase.ts (2)
hasOrgRightApikey(269-286)apikeyHasOrgRight(244-248)supabase/functions/_backend/utils/webhook.ts (4)
createTestPayload(382-400)createDeliveryRecord(125-152)deliverWebhook(157-225)updateDeliveryResult(230-252)
supabase/functions/_backend/public/webhooks/delete.ts (2)
supabase/functions/_backend/utils/hono.ts (1)
simpleError(247-249)supabase/functions/_backend/utils/supabase.ts (2)
hasOrgRightApikey(269-286)apikeyHasOrgRight(244-248)
supabase/functions/_backend/public/webhooks/post.ts (3)
supabase/functions/_backend/utils/hono.ts (1)
simpleError(247-249)supabase/functions/_backend/utils/supabase.ts (2)
hasOrgRightApikey(269-286)apikeyHasOrgRight(244-248)supabase/functions/_backend/utils/webhook.ts (1)
WEBHOOK_EVENT_TYPES(70-75)
tests/webhooks.test.ts (1)
tests/test-utils.ts (4)
getSupabaseClient(324-343)TEST_EMAIL(54-54)USER_ID(45-45)BASE_URL(19-19)
supabase/functions/_backend/public/webhooks/get.ts (3)
supabase/functions/_backend/utils/hono.ts (1)
simpleError(247-249)supabase/functions/_backend/utils/supabase.ts (2)
hasOrgRightApikey(269-286)apikeyHasOrgRight(244-248)supabase/functions/_backend/utils/utils.ts (1)
fetchLimit(13-13)
supabase/functions/_backend/public/webhooks/index.ts (8)
supabase/functions/_backend/utils/hono_middleware.ts (1)
middlewareKey(198-286)scripts/snippet/cloudflare-snippet-filter-appid.js (1)
body(65-65)supabase/functions/_backend/utils/hono.ts (1)
getBodyOrQuery(87-106)supabase/functions/_backend/public/webhooks/get.ts (1)
get(26-103)supabase/functions/_backend/public/webhooks/post.ts (1)
post(16-69)supabase/functions/_backend/public/webhooks/put.ts (1)
put(17-98)supabase/functions/_backend/public/webhooks/delete.ts (1)
deleteWebhook(12-57)supabase/functions/_backend/public/webhooks/deliveries.ts (2)
getDeliveries(26-94)retryDelivery(96-164)
supabase/functions/_backend/utils/webhook.ts (3)
src/stores/webhooks.ts (1)
WEBHOOK_EVENT_TYPES(60-65)supabase/functions/_backend/utils/logging.ts (2)
serializeError(17-27)cloudlog(3-15)supabase/functions/_backend/utils/pg.ts (1)
closeClient(205-210)
src/stores/webhooks.ts (3)
supabase/functions/_backend/utils/webhook.ts (2)
Webhook(37-47)WebhookDelivery(50-67)src/services/supabase.ts (1)
useSupabase(58-71)src/stores/organization.ts (1)
useOrganizationStore(19-317)
supabase/functions/webhooks/index.ts (5)
supabase/functions/_backend/utils/hono.ts (1)
createHono(137-196)supabase/functions/_backend/utils/version.ts (1)
version(1-1)supabase/functions/_backend/public/webhooks/index.ts (1)
app(11-11)supabase/functions/_backend/triggers/webhook_delivery.ts (1)
app(19-19)supabase/functions/_backend/triggers/webhook_dispatcher.ts (1)
app(15-15)
🪛 GitHub Actions: Run tests
supabase/functions/_backend/public/webhooks/delete.ts
[error] 30-30: TS2769: No overload matches this call. Argument of type '"webhooks"' is not assignable to parameter of type '"apps" | ... | "version_usage"'.
[error] 39-39: TS2339: Property 'org_id' does not exist on type 'SelectQueryError<...>'.
[error] 45-45: TS2769: No overload matches this call. Argument of type '"webhooks"' is not assignable to parameter of type '"apps" | ... | "version_usage"'.
tests/webhooks.test.ts
[error] 43-43: TS2769: No overload matches this call. Argument of type '"webhooks"' is not assignable to parameter of type '"apps" | ... | "version_usage"'.
[error] 209-209: TS2769: No overload matches this call. Argument of type '"webhooks"' is not assignable to parameter of type '"apps" | ... | "version_usage"'.
supabase/functions/_backend/public/webhooks/get.ts
[error] 45-45: TS2769: No overload matches this call. Argument of type '"webhooks"' is not assignable to parameter of type '"apps" | ... | "version_usage"'.
[error] 62-62: TS2769: No overload matches this call. Argument of type '"webhook_deliveries"' is not assignable to parameter of type '"usage_credit_balances" | "usage_credit_ledger"'.
supabase/functions/_backend/public/webhooks/deliveries.ts
[error] 44-44: TS2769: No overload matches this call. Argument of type '"webhooks"' is not assignable to parameter of type '"usage_credit_balances" | "usage_credit_ledger"'.
[error] 53-53: TS2339: Property 'org_id' does not exist on type 'SelectQueryError<...>'.
supabase/functions/_backend/public/webhooks/put.ts
[error] 84-84: TS2769: No overload matches this call. Argument of type '"webhooks"' is not assignable to parameter of type '"apps" | ... | "version_usage"'.
supabase/functions/_backend/utils/webhook.ts
[error] 108-108: TS2769: No overload matches this call. Argument of type '"webhooks"' is not assignable to parameter of type '"usage_credit_balances" | "usage_credit_ledger"'.
[error] 119-119: TS2322: Type '({ ... } | { ... } )[]' is not assignable to type 'Webhook[]'.
[error] 136-136: TS2769: No overload matches this call. Argument of type '"webhook_deliveries"' is not assignable to parameter of type '"usage_credit_balances" | "usage_credit_ledger"'.
🔇 Additional comments (27)
src/constants/organizationTabs.ts (1)
5-5: LGTM! Webhook tab integration follows established patterns.The webhook tab addition uses the correct heroicons import pattern and maintains consistency with existing organization tabs.
Also applies to: 15-15
supabase/functions/webhooks/index.ts (1)
1-10: LGTM! Standard edge function setup following best practices.The webhooks edge function correctly uses
createHonofrom utils, mounts the backend app, applies error handling, and serves via Deno.supabase/functions/triggers/index.ts (1)
23-24: LGTM! Webhook trigger routes properly registered.The webhook_delivery and webhook_dispatcher triggers follow the established pattern for trigger registration in this file.
Also applies to: 53-54
supabase/functions/_backend/public/webhooks/index.ts (1)
1-60: LGTM! Webhook router follows established patterns.The router correctly:
- Uses
honoFactory.createApp()as per coding guidelines- Applies
middlewareKey(['all', 'write'])to all routes (appropriate for webhook management)- Uses
getBodyOrQueryfor request parsing- Delegates to dedicated handler functions for each operation
- Follows the RESTful conventions established in the codebase
The route structure is clear and maintainable.
src/components/WebhookDeliveryLog.vue (1)
1-136: LGTM! Component follows Vue 3 and Composition API best practices.The script section correctly:
- Uses
<script setup>syntax as required- Imports from Pinia store with proper reactivity via
storeToRefs- Implements proper watchers for reactive pagination/filtering
- Uses Vue I18n for translations
- Provides good helper functions for formatting
The component logic is clean and maintainable.
supabase/functions/_backend/utils/webhook.ts (2)
423-441: pgmq schema access and parameter binding are correctly implemented.The pgmq extension is created and assigned to the pgmq schema in migrations (20250530233128_base.sql). The
webhook_deliveryqueue is explicitly created in the webhooks migration (20251226133417_webhooks.sql). Parameter binding is correct:$1accepts the queue name as text and$2::jsonbproperly casts the JSON message to JSONB type. The implementation matches the established pattern used throughout the codebase for pgmq operations.
466-496: The pgmq delay parameter support is confirmed and the code implementation is sound.The 3-parameter
pgmq.send()call with the delay parameter is a documented standard feature (available since pgmq v1.3.0+). The code correctly passes the queue name, JSON message, and delay in seconds. The fallback mechanism to immediate queueing on error is appropriate defensive programming that gracefully handles version incompatibilities or unexpected issues.supabase/functions/_backend/public/webhooks/post.ts (3)
1-6: LGTM! Imports and setup are well-structured.The imports follow project conventions and use the correct utilities for validation, error handling, and authorization.
8-14: Schema validation looks good.Proper use of Zod for request validation with appropriate constraints on name length and URL format.
16-69: Solid implementation with proper authorization checks.The endpoint correctly validates admin access, org rights, event types, and URL security before creating the webhook. The dual-check pattern (hasOrgRightApikey + apikeyHasOrgRight) provides defense in depth.
supabase/functions/_backend/triggers/webhook_dispatcher.ts (1)
29-137: Well-structured trigger with proper error handling and logging.The implementation correctly:
- Uses structured logging with requestId
- Handles invalid data gracefully with early returns
- Processes webhooks in parallel with error isolation
- Uses backgroundTask for async operations
src/components/WebhookForm.vue (1)
1-94: Good Vue 3 Composition API implementation.The component correctly uses
<script setup>, proper reactivity patterns, and handles both create and edit modes elegantly with the computedisEditingproperty.supabase/functions/_backend/public/webhooks/put.ts (1)
17-98: Good implementation pattern for partial updates.The handler correctly validates ownership, builds a dynamic update object, and handles the no-updates edge case appropriately.
src/pages/settings/organization/Webhooks.vue (2)
1-48: Well-structured component with proper state management.Good use of Pinia's
storeToRefsfor reactive store state, permission-based UI rendering, and proper lifecycle hooks for data fetching.
184-390: Clean template with good UX patterns.The template provides loading states, empty states, expandable cards, and proper action grouping. Permission checks are correctly applied to action buttons.
supabase/functions/_backend/triggers/webhook_delivery.ts (1)
40-196: Robust delivery handler with proper retry logic.The implementation correctly:
- Validates delivery data and skips completed deliveries
- Implements exponential backoff (2, 4, 8 minutes)
- Marks failed after max attempts and sends notifications
- Uses rate limiting on failure notifications
supabase/functions/_backend/public/webhooks/get.ts (2)
26-103: Good implementation with proper authorization and pagination.The handler correctly supports both single-item and list retrieval, validates access, and includes Zod parsing for response safety.
44-49: TypeScript errors: Supabase types need regeneration.Both queries reference tables not in the generated types. Regenerate types after applying the migration.
The stats calculation could also be more efficient using SQL aggregation instead of client-side filtering:
🔎 Suggested optimization for stats query
// Get recent delivery stats for this webhook - const { data: stats } = await supabaseAdmin(c) + const { count: totalCount } = await supabaseAdmin(c) .from('webhook_deliveries') - .select('status', { count: 'exact' }) + .select('*', { count: 'exact', head: true }) .eq('webhook_id', body.webhookId) .gte('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()) - const successCount = stats?.filter(s => s.status === 'success').length || 0 - const failedCount = stats?.filter(s => s.status === 'failed').length || 0 - const pendingCount = stats?.filter(s => s.status === 'pending').length || 0 + // Run separate count queries for each status + const [successRes, failedRes, pendingRes] = await Promise.all([ + supabaseAdmin(c).from('webhook_deliveries').select('*', { count: 'exact', head: true }) + .eq('webhook_id', body.webhookId).eq('status', 'success') + .gte('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()), + supabaseAdmin(c).from('webhook_deliveries').select('*', { count: 'exact', head: true }) + .eq('webhook_id', body.webhookId).eq('status', 'failed') + .gte('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()), + supabaseAdmin(c).from('webhook_deliveries').select('*', { count: 'exact', head: true }) + .eq('webhook_id', body.webhookId).eq('status', 'pending') + .gte('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()), + ])Alternatively, use a raw SQL query with
GROUP BY statusfor a single round-trip.Also applies to: 61-69
⛔ Skipped due to learnings
Learnt from: CR Repo: Cap-go/capgo PR: 0 File: .github/copilot-instructions.md:0-0 Timestamp: 2025-12-23T02:53:12.055Z Learning: Applies to cloudflare_workers/plugin/index.ts : Plugin Worker (port 8788) routes: `/updates`, `/channel_self`, `/stats`supabase/functions/_backend/public/webhooks/deliveries.ts (1)
96-164: Good retry implementation with proper state reset.The
retryDeliveryfunction correctly validates ownership, checks delivery status, verifies the webhook is enabled, resets all delivery fields, and queues for immediate delivery.tests/webhooks.test.ts (1)
1-4: LGTM - Comprehensive test coverage for webhooks API.The test suite covers all CRUD operations, validation edge cases (invalid URLs, missing fields, HTTP vs HTTPS), delivery management, and error scenarios. Good use of test isolation with random UUIDs.
src/stores/webhooks.ts (3)
67-67: Module-level Supabase client instantiation is acceptable but consider lazy initialization.The
useSupabase()call at module level works because it returns a singleton. However, if this module is imported before Supabase is configured, it could cause issues. The current pattern is common in this codebase, so this is just a note for awareness.
1-66: Well-structured types and constants.The interfaces align with backend types, and
WEBHOOK_EVENT_TYPESis properly typed withas constfor type safety. Good separation of concerns.
372-377: No action needed: frontend and backend behavior is aligned.The local state update setting
attempt_count = 0is correct. The backend'sretryDeliveryfunction (insupabase/functions/_backend/public/webhooks/deliveries.ts) explicitly setsattempt_count: 0in the database update, matching the frontend behavior. There is no UI/data inconsistency.Likely an incorrect or invalid review comment.
messages/en.json (1)
1114-1162: Webhook translation keys are comprehensive.The new i18n keys cover all webhook UI needs including CRUD operations, status messages, validation errors, and delivery-related terms. Placeholder syntax (
{name},{status}, etc.) is used consistently.supabase/migrations/20251226133417_webhooks.sql (3)
336-340: Webhook queues properly integrated into cron processing.The
webhook_dispatcherandwebhook_deliveryqueues are now included in the high-frequency (every 10 seconds) queue processing, addressing the previous review comment. This ensures timely webhook delivery.
64-205: RLS policies are well-structured.Proper separation of read vs admin permissions using
check_min_rights. No DELETE policy forwebhook_deliveriesis appropriate since cleanup is handled by the scheduledcleanup_webhook_deliveriesfunction.
215-263: Trigger function is correctly implemented.The conditional trigger creation (checking if
audit_logsexists) handles migration ordering gracefully. The payload includes all necessary audit log fields for webhook dispatch.
| import { Hono } from 'hono/tiny' | ||
| import { BRES, middlewareAPISecret } from '../utils/hono.ts' | ||
| import { cloudlog, cloudlogErr, serializeError } from '../utils/logging.ts' | ||
| import { backgroundTask } from '../utils/utils.ts' | ||
| import { | ||
|
|
||
| buildWebhookPayload, | ||
| createDeliveryRecord, | ||
| findWebhooksForEvent, | ||
| queueWebhookDelivery, | ||
| } from '../utils/webhook.ts' | ||
|
|
||
| export const app = new Hono<MiddlewareKeyVariables>() |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use createHono instead of direct new Hono() instantiation.
Per coding guidelines, all Hono apps should use createHono from utils/hono.ts for consistent initialization.
🔎 Proposed fix
-import { Hono } from 'hono/tiny'
-import { BRES, middlewareAPISecret } from '../utils/hono.ts'
+import { BRES, createHono, middlewareAPISecret } from '../utils/hono.ts'
import { cloudlog, cloudlogErr, serializeError } from '../utils/logging.ts'
import { backgroundTask } from '../utils/utils.ts'
import {
-
buildWebhookPayload,
createDeliveryRecord,
findWebhooksForEvent,
queueWebhookDelivery,
} from '../utils/webhook.ts'
-export const app = new Hono<MiddlewareKeyVariables>()
+export const app = createHono()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { Hono } from 'hono/tiny' | |
| import { BRES, middlewareAPISecret } from '../utils/hono.ts' | |
| import { cloudlog, cloudlogErr, serializeError } from '../utils/logging.ts' | |
| import { backgroundTask } from '../utils/utils.ts' | |
| import { | |
| buildWebhookPayload, | |
| createDeliveryRecord, | |
| findWebhooksForEvent, | |
| queueWebhookDelivery, | |
| } from '../utils/webhook.ts' | |
| export const app = new Hono<MiddlewareKeyVariables>() | |
| import { BRES, createHono, middlewareAPISecret } from '../utils/hono.ts' | |
| import { cloudlog, cloudlogErr, serializeError } from '../utils/logging.ts' | |
| import { backgroundTask } from '../utils/utils.ts' | |
| import { | |
| buildWebhookPayload, | |
| createDeliveryRecord, | |
| findWebhooksForEvent, | |
| queueWebhookDelivery, | |
| } from '../utils/webhook.ts' | |
| export const app = createHono() |
🤖 Prompt for AI Agents
In supabase/functions/_backend/triggers/webhook_dispatcher.ts around lines 3 to
15, the Hono app is being instantiated with new Hono(), but per project
guidelines it must use createHono from utils/hono.ts; update imports to import
createHono from '../utils/hono.ts' (remove or keep other Hono imports as needed)
and replace the direct new Hono<MiddlewareKeyVariables>() instantiation with a
call to createHono<MiddlewareKeyVariables>() so the app is initialized with the
shared setup and middleware.
| export function buildWebhookPayload(auditLogData: AuditLogData): WebhookPayload { | ||
| return { | ||
| event: `${auditLogData.table_name}.${auditLogData.operation}`, | ||
| event_id: crypto.randomUUID(), | ||
| timestamp: new Date().toISOString(), | ||
| org_id: auditLogData.org_id, | ||
| data: { | ||
| table: auditLogData.table_name, | ||
| operation: auditLogData.operation, | ||
| record_id: auditLogData.record_id, | ||
| old_record: auditLogData.old_record, | ||
| new_record: auditLogData.new_record, | ||
| changed_fields: auditLogData.changed_fields, | ||
| }, | ||
| } |
There was a problem hiding this comment.
Timestamp inconsistency: using current time instead of audit log timestamp.
Line 86 generates a new timestamp with new Date().toISOString() rather than using auditLogData.created_at. This creates a discrepancy between when the event actually occurred (audit log timestamp) and the webhook payload timestamp, potentially confusing webhook consumers about event timing.
🔎 Proposed fix
export function buildWebhookPayload(auditLogData: AuditLogData): WebhookPayload {
return {
event: `${auditLogData.table_name}.${auditLogData.operation}`,
event_id: crypto.randomUUID(),
- timestamp: new Date().toISOString(),
+ timestamp: auditLogData.created_at,
org_id: auditLogData.org_id,
data: {
table: auditLogData.table_name,
operation: auditLogData.operation,
record_id: auditLogData.record_id,
old_record: auditLogData.old_record,
new_record: auditLogData.new_record,
changed_fields: auditLogData.changed_fields,
},
}
}🤖 Prompt for AI Agents
In supabase/functions/_backend/utils/webhook.ts around lines 82 to 96, the
webhook payload uses new Date().toISOString() for timestamp causing
inconsistency with the audit log; replace that generated timestamp with the
auditLogData.created_at value (formatted/validated as an ISO string if needed)
so the payload.timestamp reflects the actual audit event time, and ensure
null/undefined created_at is handled (fallback to created_at validation or
last-known safe timestamp) before returning the payload.
| GRANT ALL ON TABLE "public"."webhooks" TO "anon"; | ||
| GRANT ALL ON TABLE "public"."webhooks" TO "authenticated"; | ||
| GRANT ALL ON TABLE "public"."webhooks" TO "service_role"; | ||
|
|
||
| -- Webhook deliveries table grants | ||
| GRANT ALL ON TABLE "public"."webhook_deliveries" TO "anon"; | ||
| GRANT ALL ON TABLE "public"."webhook_deliveries" TO "authenticated"; | ||
| GRANT ALL ON TABLE "public"."webhook_deliveries" TO "service_role"; |
There was a problem hiding this comment.
Overly permissive grants to anon role.
Granting ALL privileges on webhook tables to anon is unusual and potentially risky. While RLS policies protect the data, the anon role typically shouldn't have write access to these tables. Consider restricting to SELECT only or removing the anon grant entirely since webhooks are organization-specific authenticated features.
🔎 Suggested fix
-- Webhooks table grants
-GRANT ALL ON TABLE "public"."webhooks" TO "anon";
+GRANT SELECT ON TABLE "public"."webhooks" TO "anon";
GRANT ALL ON TABLE "public"."webhooks" TO "authenticated";
GRANT ALL ON TABLE "public"."webhooks" TO "service_role";
-- Webhook deliveries table grants
-GRANT ALL ON TABLE "public"."webhook_deliveries" TO "anon";
+GRANT SELECT ON TABLE "public"."webhook_deliveries" TO "anon";
GRANT ALL ON TABLE "public"."webhook_deliveries" TO "authenticated";
GRANT ALL ON TABLE "public"."webhook_deliveries" TO "service_role";Or if anon should have no access at all:
--- Remove anon grants entirely
-GRANT ALL ON TABLE "public"."webhooks" TO "anon";
GRANT ALL ON TABLE "public"."webhooks" TO "authenticated";
tests/webhooks.test.ts
Outdated
| afterAll(async () => { | ||
| // Clean up created webhooks | ||
| if (createdWebhookId) { | ||
| await getSupabaseClient().from('webhooks').delete().eq('id', createdWebhookId) |
There was a problem hiding this comment.
Fix TypeScript error: 'webhooks' table not in generated types.
The pipeline is failing because the webhooks table type is not recognized by the Supabase client. After adding the new migration, you need to regenerate the database types.
Run the following to regenerate types:
npx supabase gen types typescript --local > src/types/supabase.types.tsOr if using a different types path, update accordingly. This will add the new webhooks and webhook_deliveries tables to the generated Database type.
🧰 Tools
🪛 GitHub Actions: Run tests
[error] 43-43: TS2769: No overload matches this call. Argument of type '"webhooks"' is not assignable to parameter of type '"apps" | ... | "version_usage"'.
🤖 Prompt for AI Agents
In tests/webhooks.test.ts around line 43 the Supabase client TypeScript types
don't include the new 'webhooks' table causing type errors; regenerate the DB
types after adding the migration by running the supabase type generation command
(for example: npx supabase gen types typescript --local >
src/types/supabase.types.ts) or update the path to your project's types file so
the generated Database type includes 'webhooks' and 'webhook_deliveries', then
commit the updated types file.
Create checkWebhookPermission helper in index.ts and use it across all webhook endpoint handlers to reduce code duplication from 3.7% to below SonarCloud's 3% threshold. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
supabase/functions/_backend/utils/webhook.ts (1)
82-96: Use audit log timestamp in webhook payload, notnew Date()Using
new Date().toISOString()here can drift from the actual audit event time; consumers will see a timestamp that doesn’t match when the DB change occurred. Prefer the audit log’s owncreated_atso payloads reflect the real event time.Proposed change
export function buildWebhookPayload(auditLogData: AuditLogData): WebhookPayload { return { event: `${auditLogData.table_name}.${auditLogData.operation}`, event_id: crypto.randomUUID(), - timestamp: new Date().toISOString(), + timestamp: auditLogData.created_at, org_id: auditLogData.org_id, data: { table: auditLogData.table_name, operation: auditLogData.operation, record_id: auditLogData.record_id, old_record: auditLogData.old_record, new_record: auditLogData.new_record, changed_fields: auditLogData.changed_fields, }, } }supabase/functions/_backend/public/webhooks/put.ts (1)
54-60: Tighten hostname checks for non-HTTPS webhook URLsThe current check:
if (url.protocol !== 'https:' && !url.hostname.includes('localhost') && !url.hostname.includes('127.0.0.1'))treats any hostname containing
'localhost'or'127.0.0.1'as local, which can unintentionally exempt non-local domains from the HTTPS requirement.Restrict the exception to true loopback hosts (and
.localhostsubdomains if you want), e.g.:Proposed fix
if (body.url) { const url = new URL(body.url) - if (url.protocol !== 'https:' && !url.hostname.includes('localhost') && !url.hostname.includes('127.0.0.1')) { + const isLocalhost = url.hostname === 'localhost' || url.hostname.endsWith('.localhost') + const isLoopback = url.hostname === '127.0.0.1' || url.hostname === '::1' + if (url.protocol !== 'https:' && !isLocalhost && !isLoopback) { throw simpleError('invalid_url', 'Webhook URL must use HTTPS', { url: body.url }) } }supabase/functions/_backend/public/webhooks/deliveries.ts (1)
52-88: Applystatusfilter before pagination and mirror it in the count queryRight now:
- The main query orders and paginates first, then conditionally applies
.eq('status', body.status).- The total-count query ignores
statusentirely.This means filtered pages can be partially empty and
total/has_moredon’t reflect the filtered set.You can fix both by applying filters before
.order().range()and reusing the same filter in the count query:Proposed changes
// Build query const page = body.page ?? 0 const from = page * DELIVERIES_PER_PAGE const to = (page + 1) * DELIVERIES_PER_PAGE - 1 - let query = (supabaseAdmin(c) as any) - .from('webhook_deliveries') - .select('*') - .eq('webhook_id', body.webhookId) - .order('created_at', { ascending: false }) - .range(from, to) - - if (body.status) { - query = query.eq('status', body.status) - } - - const { data, error } = await query + let baseQuery = (supabaseAdmin(c) as any) + .from('webhook_deliveries') + .select('*') + .eq('webhook_id', body.webhookId) + + if (body.status) { + baseQuery = baseQuery.eq('status', body.status) + } + + const { data, error } = await baseQuery + .order('created_at', { ascending: false }) + .range(from, to) @@ - // Get total count for pagination - const { count } = await (supabaseAdmin(c) as any) - .from('webhook_deliveries') - .select('*', { count: 'exact', head: true }) - .eq('webhook_id', body.webhookId) + // Get total count for pagination (respecting same filters) + let countQuery = (supabaseAdmin(c) as any) + .from('webhook_deliveries') + .select('*', { count: 'exact', head: true }) + .eq('webhook_id', body.webhookId) + + if (body.status) { + countQuery = countQuery.eq('status', body.status) + } + + const { count } = await countQuery
🧹 Nitpick comments (1)
supabase/functions/_backend/utils/webhook.ts (1)
407-443: Queue helpers should signal failures back to callers
queueWebhookDeliveryandqueueWebhookDeliveryWithDelaycatch and log errors frompgmq.sendbut never rethrow or return a failure indicator. Callers likeretryDeliverythen happily return success even if the delivery was never queued, leaving a “pending” record with no corresponding queue message.Consider having these helpers either rethrow after logging or return a
boolean/Resultso callers can fail the API response (or at least surface a degraded status) when queueing fails.One possible pattern
export async function queueWebhookDelivery( c: Context, deliveryId: string, webhookId: string, url: string, payload: WebhookPayload, -): Promise<void> { +): Promise<boolean> { const message = { @@ const db = getPgClient(c) try { await db.query( 'SELECT pgmq.send($1, $2::jsonb)', ['webhook_delivery', JSON.stringify(message)], ) cloudlog({ requestId: c.get('requestId'), message: 'Queued webhook delivery', deliveryId, webhookId, }) + return true } catch (error) { cloudlogErr({ requestId: c.get('requestId'), message: 'Error queuing webhook delivery', error: serializeError(error) }) + return false } finally { closeClient(c, db) } }Callers can then branch on the returned boolean.
Also applies to: 449-499
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
src/types/supabase.types.tssupabase/functions/_backend/public/webhooks/delete.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/get.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/put.tssupabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/utils/webhook.tstests/webhooks.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- supabase/functions/_backend/public/webhooks/get.ts
- supabase/functions/_backend/public/webhooks/delete.ts
- tests/webhooks.test.ts
- supabase/functions/_backend/public/webhooks/post.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
src/types/supabase.types.tssupabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/public/webhooks/put.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
src/types/supabase.types.tssupabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/public/webhooks/put.ts
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}
📄 CodeRabbit inference engine (CLAUDE.md)
Mobile apps should use Capacitor with app ID
ee.forgr.capacitor_gofor native mobile functionality
Files:
src/types/supabase.types.ts
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/types/supabase.types.ts
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/types/supabase.types.ts
supabase/functions/_backend/**
📄 CodeRabbit inference engine (CLAUDE.md)
Backend logic should be organized in
supabase/functions/_backend/with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/public/webhooks/put.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Supabase Edge Functions use Deno runtime
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/public/webhooks/put.ts
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/public/webhooks/put.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/public/webhooks/put.ts
🧠 Learnings (17)
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/public/webhooks/put.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Backend code must be placed in `supabase/functions/_backend/` as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Applied to files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/index.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/public/webhooks/put.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend files
Applied to files:
supabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/public/webhooks/put.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/public/webhooks/index.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to supabase/functions/_backend/** : Backend logic should be organized in `supabase/functions/_backend/` with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Applied to files:
supabase/functions/_backend/public/webhooks/index.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to cloudflare_workers/api/index.ts : API Worker (port 8787) routes: `/bundle`, `/app`, `/device`, `/channel`, `/private/*`, `/triggers`
Applied to files:
supabase/functions/_backend/public/webhooks/index.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
supabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/public/webhooks/put.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use structured logging with `cloudlog({ requestId: c.get('requestId'), message: '...' })` for all backend logging
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Run `bun types` after schema changes to regenerate TypeScript types from Supabase
Applied to files:
supabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/public/webhooks/put.ts
📚 Learning: 2025-12-05T17:34:36.569Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-05T17:34:36.569Z
Learning: Applies to supabase/seed.sql : Update `supabase/seed.sql` to back new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
supabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Backend tests that modify local database state require a running Supabase instance
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-05T17:34:36.569Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-05T17:34:36.569Z
Learning: Investigate failing Supabase tests by reviewing Docker container logs and other relevant service logs before retrying
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to supabase/functions/**/*.ts : Supabase Edge Functions use Deno runtime
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
supabase/functions/_backend/utils/webhook.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/public/webhooks/put.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use Drizzle ORM query patterns with `schema` from `postgress_schema.ts` for all database operations; use `aliasV2()` for self-joins or multiple table references
Applied to files:
supabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-05T17:34:36.569Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-05T17:34:36.569Z
Learning: Applies to supabase/migrations/**/*.sql : A migration that introduces a new table may include seed inserts for that table, treating that seeding as part of the current feature and not modifying previously committed migrations
Applied to files:
supabase/functions/_backend/utils/supabase.types.ts
🧬 Code graph analysis (6)
src/types/supabase.types.ts (1)
supabase/functions/_backend/utils/supabase.types.ts (1)
Json(1-7)
supabase/functions/_backend/public/webhooks/test.ts (3)
supabase/functions/_backend/utils/hono.ts (1)
simpleError(247-249)supabase/functions/_backend/public/webhooks/index.ts (1)
checkWebhookPermission(19-30)supabase/functions/_backend/utils/webhook.ts (4)
createTestPayload(384-402)createDeliveryRecord(126-154)deliverWebhook(159-227)updateDeliveryResult(232-254)
supabase/functions/_backend/public/webhooks/deliveries.ts (3)
supabase/functions/_backend/utils/hono.ts (1)
simpleError(247-249)supabase/functions/_backend/public/webhooks/index.ts (1)
checkWebhookPermission(19-30)supabase/functions/_backend/utils/webhook.ts (3)
getDeliveryById(363-379)getWebhookById(342-358)queueWebhookDelivery(407-444)
supabase/functions/_backend/utils/webhook.ts (2)
supabase/functions/_backend/utils/logging.ts (2)
serializeError(17-27)cloudlog(3-15)supabase/functions/_backend/utils/pg.ts (1)
closeClient(205-210)
supabase/functions/_backend/utils/supabase.types.ts (1)
src/types/supabase.types.ts (1)
Json(1-7)
supabase/functions/_backend/public/webhooks/put.ts (3)
supabase/functions/_backend/utils/hono.ts (1)
simpleError(247-249)supabase/functions/_backend/public/webhooks/index.ts (1)
checkWebhookPermission(19-30)supabase/functions/_backend/utils/webhook.ts (1)
WEBHOOK_EVENT_TYPES(70-75)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run tests
- GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (4)
src/types/supabase.types.ts (1)
447-473: Newcache_entrytable type looks consistentThe
cache_entryRow/Insert/Update shapes are internally consistent and align with how other simple, unreferenced tables are modeled. No issues from this file’s perspective.supabase/functions/_backend/utils/supabase.types.ts (1)
447-473: Backendcache_entrytype mirrors frontend and is coherentThe new
cache_entrytable definition matches the frontend types and follows the existing pattern for simple JSON-backed tables. Looks good.supabase/functions/_backend/public/webhooks/test.ts (1)
14-89: Test webhook flow is coherent and consistent with utilitiesBody validation, org permission checks, delivery record creation, direct HTTP delivery, and result update all line up with the shared webhook utilities and data model. Using a dedicated test payload keeps behavior predictable for consumers.
supabase/functions/_backend/public/webhooks/index.ts (1)
19-79: Router wiring and shared permission helper look solid
checkWebhookPermissioncentralizes org/admin checks, and the router cleanly delegates each route to its handler behindmiddlewareKey. This matches the expected Hono + middleware pattern in this backend.
Added webhook-related translation keys to all 14 language files: - de.json (German) - es.json (Spanish) - fr.json (French) - hi.json (Hindi) - id.json (Indonesian) - it.json (Italian) - ja.json (Japanese) - ko.json (Korean) - pl.json (Polish) - pt-br.json (Portuguese) - ru.json (Russian) - tr.json (Turkish) - vi.json (Vietnamese) - zh-cn.json (Chinese) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
messages/vi.json (1)
1138-1159: LGTM! Comprehensive webhook translations added.The Vietnamese webhook translations are well-structured and grammatically correct. All 22 keys are present, technical terms are appropriately handled, and placeholders are correctly preserved.
Optional: Minor translation polish for webhook-url-hint
The translation on line 1154 is understandable but could be slightly more natural in Vietnamese:
- "webhook-url-hint": "URL HTTPS được yêu cầu cho sử dụng sản xuất", + "webhook-url-hint": "URL HTTPS bắt buộc cho môi trường sản xuất",or alternatively:
- "webhook-url-hint": "URL HTTPS được yêu cầu cho sử dụng sản xuất", + "webhook-url-hint": "URL HTTPS được yêu cầu khi sử dụng sản xuất",This is purely optional polish for a more idiomatic Vietnamese phrasing.
messages/pt-br.json (1)
1138-1159: Translation quality looks solid!The Brazilian Portuguese translations are accurate, natural, and maintain consistency with the existing translation style in the file. The technical terminology is properly handled, and interpolation variables are correctly preserved.
Optional refinement: Gender agreement
Line 1154 uses "URLs HTTPS são necessários" (masculine). In Brazilian Portuguese, "URL" is often treated as feminine, so "URLs HTTPS são necessárias" might sound slightly more natural to some speakers. However, treating foreign terms as masculine is also acceptable, so this is purely optional.
- "webhook-url-hint": "URLs HTTPS são necessários para uso em produção", + "webhook-url-hint": "URLs HTTPS são necessárias para uso em produção",If these translations haven't already been reviewed by a native Brazilian Portuguese speaker, consider having one verify them before release. This is especially important for user-facing strings in a production feature.
messages/hi.json (1)
1138-1159: Webhook translations look good!The Hindi translations for the webhooks feature are appropriate and functional. Key highlights:
- All webhook-related keys follow a consistent naming pattern
- Interpolation placeholders ({message}, {status}, {duration}) are correctly preserved
- HTTPS requirement messaging is clear and user-friendly
- Success/failure messages are well-translated
Optional refinement: There's a minor terminology variation in the file where "disabled" is translated as both "अक्षम" (line 1142, webhook-disabled) and "निष्क्रिय" (line 589, disable). While both are acceptable Hindi terms, standardizing on one throughout the file could improve consistency. This is a pre-existing pattern in the file, not introduced by your changes.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
messages/de.jsonmessages/es.jsonmessages/fr.jsonmessages/hi.jsonmessages/id.jsonmessages/it.jsonmessages/ja.jsonmessages/ko.jsonmessages/pl.jsonmessages/pt-br.jsonmessages/ru.jsonmessages/tr.jsonmessages/vi.jsonmessages/zh-cn.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (10)
messages/vi.json (1)
1137-1137: LGTM! Trailing comma correctly added.The trailing comma on the
zip-bundleline is correctly added to permit the new webhook keys that follow, maintaining valid JSON syntax.messages/ko.json (2)
1137-1137: LGTM! Trailing comma correctly added.The trailing comma on the
zip-bundleline is appropriate since new webhook entries follow.
1138-1159: LGTM! Webhook translations properly structured.The 22 new webhook-related translation keys are correctly formatted and align with the PR's webhooks feature implementation. JSON syntax is valid, placeholder variables ({message}, {status}, {duration}) are properly preserved, and key naming follows the established pattern.
messages/id.json (1)
1137-1159: LGTM! Well-structured webhook translations with consistent patterns.The webhook translation additions are properly formatted and follow established conventions in the file:
- JSON syntax is correct (trailing comma on line 1137, no trailing comma on final entry)
- Translation keys follow consistent "webhook-*" naming convention
- Indonesian translations use the same terminology patterns as existing entries ("berhasil" for success, "Gagal" for failure, etc.)
- Variable placeholders ({message}, {status}, {duration}) are correctly preserved
- Placeholder values are appropriately localized (e.g., "contoh.com" for "example.com", "Webhook Saya" for "My Webhook")
The 22 webhook-related keys provide comprehensive coverage for the webhook UI feature, including CRUD operations, validation messages, and configuration strings.
Optional: If not already done, consider having a native Indonesian speaker review these translations to ensure they are natural and idiomatic, though the patterns appear consistent with the existing file.
messages/hi.json (1)
1137-1137: Trailing comma correctly added.The trailing comma on the "zip-bundle" entry is properly placed to accommodate the new webhook translation keys that follow. JSON structure is valid.
messages/pl.json (2)
1137-1137: LGTM! Necessary JSON formatting fix.The trailing comma addition is required for valid JSON syntax since webhook translation keys are added below this entry.
1138-1159: LGTM! Comprehensive and well-localized webhook translations.The Polish translations for webhook functionality are grammatically correct and idiomatic. Key highlights:
- Covers all necessary webhook operations (CRUD, testing, validation)
- Proper variable interpolation syntax (
{message},{status},{duration})- Good localization practice: example URL uses "przyklad.com" (Polish for "example.com")
- Consistent naming convention with
webhook-*prefix- All 22 webhook keys present and matching the English reference locale
- Consistent across all 15 supported locales
messages/es.json (1)
1137-1167: Webhook i18n block looks complete and consistentThe new Spanish webhook strings cover the full UI surface (create/edit/delete, empty state, tests, URL validation) and preserve all placeholders (
{name},{message},{status},{duration}) correctly. JSON structure and tone are consistent with the rest of the file; no issues from an i18n or functional perspective.messages/fr.json (1)
1137-1167: French webhook translations are complete and well-formedThe added webhook strings cover all expected keys (create/edit/delete, confirmations, empty state, tests, URL validation) and correctly preserve placeholders (
{name},{message},{status},{duration}). JSON is valid and the phrasing matches the tone of the rest of the fr locale.messages/de.json (1)
1137-1167: LGTM! German webhook translations added successfully.The webhook-related translation keys have been properly added to the German locale file with appropriate translations.
| "zip-bundle": "Pacchetto app Zip", | ||
| "webhook-created": "Webhook creato con successo", | ||
| "webhook-create-failed": "Impossibile creare il webhook", | ||
| "webhook-deleted": "Webhook eliminato con successo", | ||
| "webhook-delete-failed": "Impossibile eliminare il webhook", | ||
| "webhook-disabled": "Webhook disabilitato", | ||
| "webhook-enabled": "Webhook abilitato", | ||
| "webhook-events": "Eventi", | ||
| "webhook-events-required": "Seleziona almeno un evento", | ||
| "webhook-name": "Nome", | ||
| "webhook-name-placeholder": "Il Mio Webhook", | ||
| "webhook-test-failed": "Test fallito: {message}", | ||
| "webhook-test-success": "Test riuscito! Stato: {status}, Durata: {duration}ms", | ||
| "webhook-toggle-failed": "Impossibile cambiare lo stato del webhook", | ||
| "webhook-updated": "Webhook aggiornato con successo", | ||
| "webhook-update-failed": "Impossibile aggiornare il webhook", | ||
| "webhook-url": "URL", | ||
| "webhook-url-hint": "È richiesto un URL HTTPS per l'uso in produzione", | ||
| "webhook-url-https-required": "L'URL del webhook deve utilizzare HTTPS", | ||
| "webhook-url-invalid": "Inserisci un URL valido", | ||
| "webhook-url-placeholder": "https://esempio.com/webhook", | ||
| "webhooks": "Webhook", | ||
| "webhooks-description": "Configura i webhook per ricevere notifiche HTTP quando si verificano eventi nella tua organizzazione." |
There was a problem hiding this comment.
Add missing UI-level webhook keys to match other locales
- The Italian webhook status and URL strings look good and preserve
{message},{status},{duration}correctly. - Compared to
messages/es.json/messages/fr.json, this locale is missing the UI-facing keysadd-webhook,create-first-webhook,create-webhook,delete-webhook,delete-webhook-confirm,edit-webhook,no-webhooks, andno-webhooks-description, so parts of the webhooks page will likely show English.
To keep i18n coverage consistent, consider adding:
Proposed additions for it‑IT
"zip-bundle": "Pacchetto app Zip",
+ "add-webhook": "Aggiungi Webhook",
+ "create-first-webhook": "Crea il tuo primo webhook",
+ "create-webhook": "Crea Webhook",
+ "delete-webhook": "Elimina Webhook",
+ "delete-webhook-confirm": "Sei sicuro di voler eliminare il webhook \"{name}\"? Questa azione non può essere annullata.",
+ "edit-webhook": "Modifica Webhook",
+ "no-webhooks": "Nessun webhook configurato",
+ "no-webhooks-description": "Configura i webhook per ricevere notifiche HTTP quando si verificano eventi.",
"webhook-created": "Webhook creato con successo",
"webhook-create-failed": "Impossibile creare il webhook",
"webhook-deleted": "Webhook eliminato con successo",
...🤖 Prompt for AI Agents
In messages/it.json around lines 1137 to 1159, the locale is missing several
UI-facing webhook keys present in other locales (add-webhook,
create-first-webhook, create-webhook, delete-webhook, delete-webhook-confirm,
edit-webhook, no-webhooks, no-webhooks-description); add these keys with
appropriate Italian translations that preserve any placeholders and tone
consistent with existing entries (e.g., concise UI copy), matching the keys and
structure used in messages/es.json and messages/fr.json so the webhooks page
displays fully localized strings.
| "webhook-created": "Webhookが正常に作成されました", | ||
| "webhook-create-failed": "Webhookの作成に失敗しました", | ||
| "webhook-deleted": "Webhookが正常に削除されました", | ||
| "webhook-delete-failed": "Webhookの削除に失敗しました", | ||
| "webhook-disabled": "Webhookが無効化されました", | ||
| "webhook-enabled": "Webhookが有効化されました", | ||
| "webhook-events": "イベント", | ||
| "webhook-events-required": "少なくとも1つのイベントを選択してください", | ||
| "webhook-name": "名前", | ||
| "webhook-name-placeholder": "マイWebhook", | ||
| "webhook-test-failed": "テスト失敗: {message}", | ||
| "webhook-test-success": "テスト成功!ステータス: {status}、所要時間: {duration}ms", | ||
| "webhook-toggle-failed": "Webhookの切り替えに失敗しました", | ||
| "webhook-updated": "Webhookが正常に更新されました", | ||
| "webhook-update-failed": "Webhookの更新に失敗しました", | ||
| "webhook-url": "URL", | ||
| "webhook-url-hint": "本番環境ではHTTPS URLが必要です", | ||
| "webhook-url-https-required": "Webhook URLはHTTPSを使用する必要があります", | ||
| "webhook-url-invalid": "有効なURLを入力してください", | ||
| "webhook-url-placeholder": "https://example.com/webhook", | ||
| "webhooks": "Webhook", | ||
| "webhooks-description": "組織でイベントが発生した時にHTTP通知を受け取るようにWebhookを設定します。" |
There was a problem hiding this comment.
Missing webhook translation keys for Japanese locale.
The Japanese translation file is missing 8 webhook-related keys that are present in the German translation file (messages/de.json, lines 1138-1145). This will result in incomplete localization for Japanese users.
Missing keys:
add-webhookcreate-first-webhookcreate-webhookdelete-webhookdelete-webhook-confirmedit-webhookno-webhooksno-webhooks-description
These keys appear to be UI labels and messages for the webhook management interface. Without them, Japanese users will see untranslated text (likely English fallback or raw key names).
🔎 Recommended additions for Japanese locale
Add the following keys to complete the Japanese webhook translations:
"zip-bundle": "Zipアプリバンドル",
+ "add-webhook": "Webhookを追加",
+ "create-first-webhook": "最初のWebhookを作成",
+ "create-webhook": "Webhookを作成",
+ "delete-webhook": "Webhookを削除",
+ "delete-webhook-confirm": "Webhook「{name}」を削除してもよろしいですか?この操作は元に戻せません。",
+ "edit-webhook": "Webhookを編集",
+ "no-webhooks": "Webhookが設定されていません",
+ "no-webhooks-description": "イベントが発生したときにHTTP通知を受け取るようにWebhookを設定します。",
"webhook-created": "Webhookが正常に作成されました",Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In messages/ja.json around lines 1138 to 1159, eight webhook-related translation
keys are missing (add-webhook, create-first-webhook, create-webhook,
delete-webhook, delete-webhook-confirm, edit-webhook, no-webhooks,
no-webhooks-description); add these keys with appropriate Japanese translations
into the same block of webhook entries so the locale matches messages/de.json,
using concise UI-friendly Japanese strings for button labels, confirmations and
empty-state descriptions consistent with existing style and punctuation.
| "zip-bundle": "Приложение в формате Zip", | ||
| "webhook-created": "Вебхук успешно создан", | ||
| "webhook-create-failed": "Не удалось создать вебхук", | ||
| "webhook-deleted": "Вебхук успешно удален", | ||
| "webhook-delete-failed": "Не удалось удалить вебхук", | ||
| "webhook-disabled": "Вебхук отключен", | ||
| "webhook-enabled": "Вебхук включен", | ||
| "webhook-events": "События", | ||
| "webhook-events-required": "Выберите хотя бы одно событие", | ||
| "webhook-name": "Название", | ||
| "webhook-name-placeholder": "Мой вебхук", | ||
| "webhook-test-failed": "Тест не пройден: {message}", | ||
| "webhook-test-success": "Тест успешен! Статус: {status}, Длительность: {duration}мс", | ||
| "webhook-toggle-failed": "Не удалось переключить вебхук", | ||
| "webhook-updated": "Вебхук успешно обновлен", | ||
| "webhook-update-failed": "Не удалось обновить вебхук", | ||
| "webhook-url": "URL", | ||
| "webhook-url-hint": "Для производственного использования требуются URL-адреса HTTPS", | ||
| "webhook-url-https-required": "URL вебхука должен использовать HTTPS", | ||
| "webhook-url-invalid": "Введите действительный URL", | ||
| "webhook-url-placeholder": "https://example.com/webhook", | ||
| "webhooks": "Вебхуки", | ||
| "webhooks-description": "Настройте вебхуки для получения HTTP-уведомлений при возникновении событий в вашей организации." |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the messages directory and verify both files exist
echo "=== Locating translation files ==="
find . -name "en.json" -o -name "ru.json" | head -20
echo -e "\n=== Checking webhook keys in en.json ==="
grep -o '"webhook-[^"]*"' messages/en.json 2>/dev/null | sort | uniq
echo -e "\n=== Checking webhook keys in ru.json ==="
grep -o '"webhook-[^"]*"' messages/ru.json 2>/dev/null | sort | uniq
echo -e "\n=== Comparing webhook key counts ==="
EN_KEYS=$(grep -o '"webhook-[^"]*"' messages/en.json 2>/dev/null | sort | uniq | wc -l)
RU_KEYS=$(grep -o '"webhook-[^"]*"' messages/ru.json 2>/dev/null | sort | uniq | wc -l)
echo "English webhook keys: $EN_KEYS"
echo "Russian webhook keys: $RU_KEYS"
if [ "$EN_KEYS" -eq "$RU_KEYS" ]; then
echo "✓ Key counts match!"
else
echo "✗ Key counts differ!"
fi
echo -e "\n=== Checking for missing keys in ru.json ==="
EN_KEYS_LIST=$(grep -o '"webhook-[^"]*"' messages/en.json 2>/dev/null | sort | uniq)
RU_KEYS_LIST=$(grep -o '"webhook-[^"]*"' messages/ru.json 2>/dev/null | sort | uniq)
comm -23 <(echo "$EN_KEYS_LIST") <(echo "$RU_KEYS_LIST")
echo -e "\n=== Validating JSON syntax in ru.json ==="
python3 -c "import json; json.load(open('messages/ru.json'))" && echo "✓ ru.json is valid JSON" || echo "✗ ru.json has JSON syntax errors"
echo -e "\n=== Validating JSON syntax in en.json ==="
python3 -c "import json; json.load(open('messages/en.json'))" && echo "✓ en.json is valid JSON" || echo "✗ en.json has JSON syntax errors"Repository: Cap-go/capgo
Length of output: 142
🏁 Script executed:
# Quick verification of the specific file and lines
echo "=== Checking if messages directory exists ==="
ls -la messages/ 2>/dev/null | head -10
echo -e "\n=== File sizes ==="
wc -l messages/*.json 2>/dev/null | grep -E "(en|ru)\.json"
echo -e "\n=== Checking lines 1137-1159 in ru.json ==="
sed -n '1137,1159p' messages/ru.json
echo -e "\n=== Quick JSON syntax check on specific lines ==="
sed -n '1135,1161p' messages/ru.json | python3 -m json.tool > /tmp/test.json 2>&1 && echo "✓ Valid JSON section" || echo "✗ JSON error"
echo -e "\n=== Sample: webhook keys from en.json (first 5) ==="
grep '"webhook-' messages/en.json 2>/dev/null | head -5
echo -e "\n=== Sample: webhook keys from ru.json (first 5) ==="
grep '"webhook-' messages/ru.json 2>/dev/null | head -5Repository: Cap-go/capgo
Length of output: 2683
🏁 Script executed:
echo "=== Validating complete JSON files ==="
python3 << 'EOF'
import json
import sys
files = ['messages/en.json', 'messages/ru.json']
for f in files:
try:
with open(f) as file:
data = json.load(file)
print(f"✓ {f} is valid JSON ({len(data)} keys)")
except json.JSONDecodeError as e:
print(f"✗ {f} has JSON error: {e}")
sys.exit(1)
EOF
echo -e "\n=== Extracting all webhook keys ==="
python3 << 'EOF'
import json
with open('messages/en.json') as f:
en = json.load(f)
with open('messages/ru.json') as f:
ru = json.load(f)
en_webhook = sorted([k for k in en.keys() if 'webhook' in k])
ru_webhook = sorted([k for k in ru.keys() if 'webhook' in k])
print(f"English webhook keys: {len(en_webhook)}")
print(f"Russian webhook keys: {len(ru_webhook)}")
missing_in_ru = set(en_webhook) - set(ru_webhook)
if missing_in_ru:
print(f"\n✗ Missing in ru.json: {missing_in_ru}")
else:
print("\n✓ All English webhook keys have Russian translations")
extra_in_ru = set(ru_webhook) - set(en_webhook)
if extra_in_ru:
print(f"⚠ Extra in ru.json: {extra_in_ru}")
print(f"\nEnglish webhook keys:\n{chr(10).join(en_webhook)}")
print(f"\nRussian webhook keys:\n{chr(10).join(ru_webhook)}")
EOFRepository: Cap-go/capgo
Length of output: 1469
Missing 8 webhook translation keys in Russian locale.
The Russian translation file is incomplete. While the webhook-* operation keys are present (create, update, delete success/failure, testing, validation), the following 8 keys from the English source are missing:
add-webhookcreate-webhookcreate-first-webhookdelete-webhookdelete-webhook-confirmedit-webhookno-webhooksno-webhooks-description
These are critical UI labels for actions, buttons, and empty states. All 30 English webhook keys must be translated to complete the feature localization.
🤖 Prompt for AI Agents
In messages/ru.json around lines 1137 to 1159 the webhook section is missing
eight keys present in English (add-webhook, create-webhook,
create-first-webhook, delete-webhook, delete-webhook-confirm, edit-webhook,
no-webhooks, no-webhooks-description); add these keys with appropriate Russian
translations matching tone and terminology used in the file (e.g., "Добавить
вебхук", "Создать вебхук", "Создайте первый вебхук, чтобы получать уведомления",
"Удалить вебхук", "Вы уверены, что хотите удалить этот вебхук?", "Редактировать
вебхук", "Вебхуков нет", and a short descriptive sentence for
no-webhooks-description), place them with the existing webhook keys, and ensure
correct JSON formatting and proper escaping of any punctuation.
| "zip-bundle": "ZIP应用程序包", | ||
| "webhook-created": "Webhook创建成功", | ||
| "webhook-create-failed": "创建Webhook失败", | ||
| "webhook-deleted": "Webhook删除成功", | ||
| "webhook-delete-failed": "删除Webhook失败", | ||
| "webhook-disabled": "Webhook已禁用", | ||
| "webhook-enabled": "Webhook已启用", | ||
| "webhook-events": "事件", | ||
| "webhook-events-required": "请至少选择一个事件", | ||
| "webhook-name": "名称", | ||
| "webhook-name-placeholder": "我的Webhook", | ||
| "webhook-test-failed": "测试失败: {message}", | ||
| "webhook-test-success": "测试成功!状态: {status}, 耗时: {duration}毫秒", | ||
| "webhook-toggle-failed": "切换Webhook失败", | ||
| "webhook-updated": "Webhook更新成功", | ||
| "webhook-update-failed": "更新Webhook失败", | ||
| "webhook-url": "URL", | ||
| "webhook-url-hint": "生产环境需要使用HTTPS URL", | ||
| "webhook-url-https-required": "Webhook URL必须使用HTTPS", | ||
| "webhook-url-invalid": "请输入有效的URL", | ||
| "webhook-url-placeholder": "https://example.com/webhook", | ||
| "webhooks": "Webhooks", | ||
| "webhooks-description": "配置Webhooks以在您的组织发生事件时接收HTTP通知。" |
There was a problem hiding this comment.
Webhook translations look correct, but key coverage is incomplete vs other locales
- JSON is valid and the new messages read well; placeholders
{message},{status},{duration}are correctly preserved. - However, compared to
messages/es.json/messages/fr.json, this locale is missing several webhook UI keys such asadd-webhook,create-first-webhook,create-webhook,delete-webhook,delete-webhook-confirm,edit-webhook,no-webhooks, andno-webhooks-description. Those are likely used by the webhooks page and will fall back to English for zh-CN.
Consider adding the missing keys to keep zh-CN on par with the other locales. For example:
Proposed additions for zh‑CN
"zip-bundle": "ZIP应用程序包",
+ "add-webhook": "添加 Webhook",
+ "create-first-webhook": "创建您的第一个 Webhook",
+ "create-webhook": "创建 Webhook",
+ "delete-webhook": "删除 Webhook",
+ "delete-webhook-confirm": "确定要删除 Webhook“{name}”吗?此操作无法撤销。",
+ "edit-webhook": "编辑 Webhook",
+ "no-webhooks": "尚未配置任何 Webhook",
+ "no-webhooks-description": "配置 Webhook,在组织发生事件时接收 HTTP 通知。",
"webhook-created": "Webhook创建成功",
"webhook-create-failed": "创建Webhook失败",
"webhook-deleted": "Webhook删除成功",
"webhook-delete-failed": "删除Webhook失败",
...Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In messages/zh-cn.json around lines 1137 to 1159 the webhook section is missing
several keys present in other locales which will cause fallback to English; add
the following keys with Chinese translations to match coverage: "add-webhook",
"create-first-webhook", "create-webhook", "delete-webhook",
"delete-webhook-confirm", "edit-webhook", "no-webhooks", and
"no-webhooks-description"; ensure translations are accurate, preserve any
placeholders if used in other locales, and place them near the existing webhook
keys to keep the file organized.
- Remove duplicate translation keys in en.json (disable, enable, enabled) - Fix URL hostname validation in post.ts, put.ts, and WebhookForm.vue - Use exact hostname matching instead of includes() for security - Support localhost, .localhost subdomains, 127.0.0.1, and ::1 - Fix inverted toast message after webhook toggle in Webhooks.vue - Remove unused selectedWebhook state from webhooks store - Fix filter ordering in deliveries.ts - apply status filter before range - Fix count query in deliveries.ts to include status filter for pagination 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The process_manifest_bundle_counts_queue function was dropped in a previous migration (20251120150750_simplify_manifest_bundle_counts.sql). Update the process_all_cron_tasks function to match current production: - Remove call to process_manifest_bundle_counts_queue (function no longer exists) - Add on_version_create and credit_usage_alerts to high-frequency queue - Remove on_version_create from low-frequency queue (now in high-frequency) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (4)
messages/zh-cn.json (1)
1178-1200: Webhook status strings look good, but key coverage is still incomplete for zh‑CNThe new webhook strings (
webhook-*,webhooks,webhooks-description) are consistent and preserve{message},{status},{duration}correctly. However, the zh‑CN locale still lacks several UI-facing keys used byWebhooks.vue, so parts of the webhooks page will fall back to English:
add-webhookcreate-first-webhookcreate-webhookdelete-webhookdelete-webhook-confirmedit-webhookno-webhooksno-webhooks-descriptionsubscribed-eventstestview-deliveriesConsider adding these near the existing webhook block (mirroring
messages/en.json/messages/es.json), preserving placeholders such as{name}where applicable, so the Webhooks UI is fully localized in zh‑CN.messages/it.json (1)
1178-1200: Italian webhook translations are fine, but core Webhooks UI keys are still missingThe added Italian strings for webhook lifecycle and URL/status look accurate and keep
{message},{status},{duration}intact. However, this locale is still missing several keys thatWebhooks.vuerelies on for the main UI:
add-webhookcreate-first-webhookcreate-webhookdelete-webhookdelete-webhook-confirmedit-webhookno-webhooksno-webhooks-descriptionsubscribed-eventstestview-deliveriesWithout these, those labels will display in English. To keep it-IT on par with other locales, add these keys with concise Italian translations matching
messages/en.jsonsemantics and reusing any placeholders (e.g.{name}).messages/ja.json (1)
1178-1200: Webhook messages added, but high-level webhook UI keys still missing in JapaneseThe newly added
webhook-*andwebhooks*strings look consistent, but the higher‑level UI keys used for buttons and empty states are still missing in this locale (they exist inmessages/de.jsonandmessages/es.json):
add-webhookcreate-first-webhookcreate-webhookdelete-webhookdelete-webhook-confirmedit-webhookno-webhooksno-webhooks-descriptionWithout these, Japanese users will see fallbacks for key parts of the webhook UI. Consider adding them alongside the existing webhook keys here.
supabase/migrations/20251226133417_webhooks.sql (1)
302-309: Overly permissive grants toanonrole remain.As previously noted, granting
ALLprivileges on webhook tables toanonis overly permissive. While RLS policies protect the data, webhooks are organization-specific authenticated features andanonshould have no access to these tables.🔎 Recommended fix
-- Webhooks table grants -GRANT ALL ON TABLE "public"."webhooks" TO "anon"; GRANT ALL ON TABLE "public"."webhooks" TO "authenticated"; GRANT ALL ON TABLE "public"."webhooks" TO "service_role"; -- Webhook deliveries table grants -GRANT ALL ON TABLE "public"."webhook_deliveries" TO "anon"; GRANT ALL ON TABLE "public"."webhook_deliveries" TO "authenticated"; GRANT ALL ON TABLE "public"."webhook_deliveries" TO "service_role";
🧹 Nitpick comments (4)
src/pages/settings/organization/Webhooks.vue (3)
45-52: Avoid duplicatefetchWebhookscalls on initial load
onMountedcallswebhooksStore.fetchWebhooks(), and thewatch(currentOrganization, …)will also fire when the org is first resolved, likely causing two back‑to‑back fetches on page load.Consider driving all loading from the watcher with
{ immediate: true }and dropping the explicit call inonMounted, or adding a simple guard in the watcher to skip when the organization hasn’t actually changed.
126-142: MaketestingWebhookIdclearing more defensive
testWebhooksets and clearstestingWebhookIdaround the async call, which is good for the loading state. IfwebhooksStore.testWebhookwere ever to throw (network error not wrapped into{ success: false }),testingWebhookIdwould never be reset and the spinner would stick for that webhook.A small defensive improvement is to use
try/finally:async function testWebhook(webhook: Webhook) { if (!hasPermission.value) { toast.error(t('no-permission')) return } testingWebhookId.value = webhook.id try { const result = await webhooksStore.testWebhook(webhook.id) if (result.success) { toast.success(t('webhook-test-success', { status: result.status, duration: result.duration_ms })) } else { toast.error(t('webhook-test-failed', { message: result.message })) } } finally { testingWebhookId.value = null } }Not critical, but it hardens the UI against unexpected exceptions.
199-207: Align button styling with shared/DaisyUI patterns to reduce duplicationAll the actions on this page (
Add webhook, empty‑state CTA, Test / View deliveries / Enable‑Disable / Edit / Delete) are hand‑styled with raw Tailwind classes. It works, but:
- There’s a lot of duplicated class strings across buttons.
- Project guidelines prefer DaisyUI (
d-prefixed classes) for interactive elements to keep behavior and spacing consistent.When convenient, consider refactoring these to a shared button pattern (e.g.
d-btnvariants or a smallButtoncomponent) so webhooks UI matches the rest of the app and future style changes don’t require updating many call sites.Also applies to: 230-236, 323-363
src/stores/webhooks.ts (1)
7-427: Solid Pinia store for webhooks; consider small type/DRY improvementsThis store cleanly encapsulates webhook CRUD, testing, and delivery logs around the current organization, with appropriate loading flags and local list updates. The flow via Supabase edge functions and the
webhooks/deliveriesstate looks consistent and side‑effect free.Two optional refinements you might consider:
- Extract a tiny helper like
getOrgIdOrError()to avoid repeating theuseOrganizationStore().currentOrganization?.gid+ “No organization selected” handling across each action.- Narrow
WebhookDelivery.request_payloadfromanyonce you settle on a stable payload shape, to catch mismatches between backend and frontend early.Otherwise this aligns well with the Pinia store guidance and the rest of the codebase style. Based on learnings, centralized Pinia state here is appropriate.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
messages/de.jsonmessages/en.jsonmessages/es.jsonmessages/fr.jsonmessages/hi.jsonmessages/id.jsonmessages/it.jsonmessages/ja.jsonmessages/ko.jsonmessages/pl.jsonmessages/pt-br.jsonmessages/ru.jsonmessages/tr.jsonmessages/vi.jsonmessages/zh-cn.jsonsrc/components/WebhookForm.vuesrc/constants/organizationTabs.tssrc/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.tssupabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/post.tssupabase/functions/_backend/public/webhooks/put.tssupabase/migrations/20251226133417_webhooks.sql
🚧 Files skipped from review as they are similar to previous changes (11)
- supabase/functions/_backend/public/webhooks/post.ts
- src/constants/organizationTabs.ts
- supabase/functions/_backend/public/webhooks/put.ts
- messages/fr.json
- src/components/WebhookForm.vue
- messages/id.json
- messages/ru.json
- messages/tr.json
- supabase/functions/_backend/public/webhooks/deliveries.ts
- messages/pl.json
- messages/en.json
🧰 Additional context used
📓 Path-based instructions (14)
src/stores/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Pinia stores for state management
Files:
src/stores/webhooks.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vue
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
src/stores/webhooks.ts
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}
📄 CodeRabbit inference engine (CLAUDE.md)
Mobile apps should use Capacitor with app ID
ee.forgr.capacitor_gofor native mobile functionality
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vue
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vue
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vue
src/**/*.{vue,ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
Konsta components are reserved for the safe area helpers; avoid importing
konstaanywhere else in the app
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vue
src/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.vue: Use Vue 3 with Composition API and<script setup>syntax for frontend components
Style components using TailwindCSS with DaisyUI components
src/**/*.vue: Use Vue 3<script setup>syntax exclusively for all Vue component scripts
Use Tailwind utility classes for layout and spacing in Vue components
Use DaisyUI components (d-btn,d-input,d-card) for interactive elements in Vue components
Use Konsta components ONLY for safe area helpers (top/bottom insets) in Vue components; avoid other uses
UseuseRoute()fromvue-routerto access route parameters anduseRouter()for programmatic navigation in Vue componentsUse DaisyUI (
d-prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent
Files:
src/pages/settings/organization/Webhooks.vue
src/pages/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
Use file-based routing with unplugin-vue-router for frontend pages
Frontend file-based routing uses
src/pages/directory structure; routes auto-generate withunplugin-vue-routerand types are available insrc/typed-router.d.ts
Files:
src/pages/settings/organization/Webhooks.vue
src/**/*.{vue,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Files:
src/pages/settings/organization/Webhooks.vue
src/**/*.{css,scss,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Mirror the Capgo design palette from
src/styles/style.css(e.g.,--color-primary-500: #515271,--color-azure-500: #119eff) when introducing new UI, using deep slate bases with the Extract azure highlight and soft radii
Files:
src/pages/settings/organization/Webhooks.vue
supabase/migrations/**/*.sql
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Database migrations must be created with
supabase migration new <feature_slug>and never modify previously committed migrations
Files:
supabase/migrations/20251226133417_webhooks.sql
**/{migrations,tests,__tests__}/**/*.{sql,ts,js}
📄 CodeRabbit inference engine (AGENTS.md)
Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows
Files:
supabase/migrations/20251226133417_webhooks.sql
supabase/migrations/*.sql
📄 CodeRabbit inference engine (AGENTS.md)
supabase/migrations/*.sql: When creating schema changes, usesupabase migration new <feature_slug>to create a single migration file and keep editing that file until the feature ships; never edit previously committed migrations
A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Do not create new cron jobs; instead update theprocess_all_cron_tasksfunction in a new migration file to add your job if needed
Files:
supabase/migrations/20251226133417_webhooks.sql
🧠 Learnings (10)
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/stores/**/*.{ts,tsx} : Use Pinia stores for state management
Applied to files:
src/stores/webhooks.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/**/*.vue : Use Vue 3 with Composition API and `<script setup>` syntax for frontend components
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to src/**/*.{vue,css,scss} : The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-23T01:19:04.593Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:04.593Z
Learning: In the Cap-go codebase, ensure that app permission checks never include the role 'owner'. App-level permissions should be based on the user_min_right enum with values: read, upload, write, admin, super_admin (and NOT owner). This pattern applies across Vue components that perform permission checks; if you find a check referencing 'owner' for app-level access, replace it with the appropriate user_min_right value and keep organization-level owner handling in organization.ts.
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/seed.sql : Update `supabase/seed.sql` to back new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/migrations/*.sql : Do not create new cron jobs; instead update the `process_all_cron_tasks` function in a new migration file to add your job if needed
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/migrations/*.sql : A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/migrations/**/*.sql : Database migrations must be created with `supabase migration new <feature_slug>` and never modify previously committed migrations
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
📚 Learning: 2025-12-24T14:11:10.256Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539
Timestamp: 2025-12-24T14:11:10.256Z
Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
📚 Learning: 2025-12-25T11:22:13.039Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:13.039Z
Learning: In SQL migrations under the repository (e.g., supabase/migrations), enforce that when an org has enforcing_2fa=true, all users (including super_admins) must have 2FA enabled before accessing any org functions, including check_org_members_2fa_enabled. Do not grant admin exceptions to 2FA requirements. This ensures consistent security enforcement across all org-related operations; implement this rule within relevant migrations and associated stored procedures/tests.
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
🧬 Code graph analysis (1)
src/stores/webhooks.ts (2)
supabase/functions/_backend/utils/webhook.ts (2)
Webhook(37-47)WebhookDelivery(50-67)src/services/supabase.ts (1)
useSupabase(58-71)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (9)
messages/pt-br.json (1)
1178-1200: LGTM! High-quality Brazilian Portuguese translations.The webhook translations are clear, accurate, and natural. All success/failure messages, field labels, placeholders, and validation messages are contextually appropriate and follow consistent patterns with the rest of the translation file.
messages/de.json (1)
1178-1208: German webhook translations look complete and consistentThe added webhook‑related keys (
add-webhook,create-webhook, delete/edit/empty‑state strings, plus allwebhook-*messages) are well-phrased and aligned with the existing German tone and terminology. This should fully cover the new webhook UI in this locale.messages/es.json (1)
1178-1208: Spanish webhook strings cover full webhook UI surfaceAll expected webhook keys are present (
add-webhook, create/delete/edit, empty‑state texts, and the fullwebhook-*set), and the translations read naturally alongside the rest of the Spanish locale. No issues from a localization/completeness standpoint.supabase/migrations/20251226133417_webhooks.sql (6)
8-56: LGTM: Well-structured webhook tables with appropriate indexes.The schema design is solid with proper foreign keys, cascade deletes, and efficient indexing including a partial index for pending retries. The nullable
audit_log_idcorrectly supports test events.
61-204: LGTM: RLS policies properly enforce organization-level access control.The policies correctly use
check_min_rightsto enforce 'read' access for viewing and 'admin' access for mutations. Service role full access is appropriate for background processing.
209-243: LGTM: Safe SECURITY DEFINER trigger for webhook dispatch.The trigger function correctly uses
SECURITY DEFINERto enqueue audit log events. The function only reads trigger data and contains no user input or dynamic SQL, making it safe.
249-263: LGTM: Conditional trigger creation handles migration dependencies.The conditional check for
audit_logstable existence is good practice for migrations that may run in different orders.
286-295: LGTM: Safe cleanup function with reasonable retention.The 7-day retention is appropriate for delivery logs. The
SECURITY DEFINERis necessary for cron execution and the function performs a simple, safe DELETE operation.
320-507: LGTM: Webhook queues properly integrated into cron processing.The webhook system is correctly integrated:
- Both
webhook_dispatcherandwebhook_deliveryqueues are processed in the high-frequency block (every 10 seconds) on line 337- The
cleanup_webhook_deliveries()function is scheduled for daily execution at midnight (lines 431-435)- Exception handling ensures task failures don't block other cron jobs
This addresses the previous concern about webhook queues not being scheduled.
| "zip-bundle": "ज़िप ऐप बंडल", | ||
| "webhook-created": "वेबहुक सफलतापूर्वक बनाया गया", | ||
| "webhook-create-failed": "वेबहुक बनाने में विफल", | ||
| "webhook-deleted": "वेबहुक सफलतापूर्वक हटा दिया गया", | ||
| "webhook-delete-failed": "वेबहुक हटाने में विफल", | ||
| "webhook-disabled": "वेबहुक अक्षम", | ||
| "webhook-enabled": "वेबहुक सक्षम", | ||
| "webhook-events": "इवेंट्स", | ||
| "webhook-events-required": "कृपया कम से कम एक इवेंट चुनें", | ||
| "webhook-name": "नाम", | ||
| "webhook-name-placeholder": "मेरा वेबहुक", | ||
| "webhook-test-failed": "परीक्षण विफल: {message}", | ||
| "webhook-test-success": "परीक्षण सफल! स्थिति: {status}, अवधि: {duration}ms", | ||
| "webhook-toggle-failed": "वेबहुक टॉगल करने में विफल", | ||
| "webhook-updated": "वेबहुक सफलतापूर्वक अपडेट किया गया", | ||
| "webhook-update-failed": "वेबहुक अपडेट करने में विफल", | ||
| "webhook-url": "URL", | ||
| "webhook-url-hint": "प्रोडक्शन उपयोग के लिए HTTPS URL आवश्यक हैं", | ||
| "webhook-url-https-required": "वेबहुक URL को HTTPS का उपयोग करना चाहिए", | ||
| "webhook-url-invalid": "कृपया एक वैध URL दर्ज करें", | ||
| "webhook-url-placeholder": "https://example.com/webhook", | ||
| "webhooks": "वेबहुक्स", | ||
| "webhooks-description": "जब आपके संगठन में घटनाएं हों तो HTTP सूचनाएं प्राप्त करने के लिए वेबहुक कॉन्फ़िगर करें।" |
There was a problem hiding this comment.
Hindi webhook messages are correct but Webhooks UI labels are incomplete
The new Hindi webhook strings (webhook-*, webhooks, webhooks-description) read well and preserve placeholders like {message}, {status}, {duration}.
Webhooks.vue also uses UI-level keys that are not present in this file, e.g.:
add-webhook,create-first-webhook,create-webhookdelete-webhook,delete-webhook-confirm,edit-webhookno-webhooks,no-webhooks-descriptionsubscribed-events,test,view-deliveries
These will currently fall back to English. For a fully localized experience, consider adding Hindi translations for those keys near the existing webhook block, mirroring messages/en.json.
🤖 Prompt for AI Agents
In messages/hi.json around lines 1178 to 1200, the Hindi localization includes
many webhook keys but is missing several UI-level keys used by Webhooks.vue
(add-webhook, create-first-webhook, create-webhook, delete-webhook,
delete-webhook-confirm, edit-webhook, no-webhooks, no-webhooks-description,
subscribed-events, test, view-deliveries), causing fallback to English; add
these missing keys as Hindi translations adjacent to the existing webhook block,
mirroring the keys and phrasing from messages/en.json and keeping placeholders
and punctuation intact so the Webhooks UI is fully localized.
| "zip-bundle": "지프 앱 번들", | ||
| "webhook-created": "웹훅이 성공적으로 생성되었습니다", | ||
| "webhook-create-failed": "웹훅 생성에 실패했습니다", | ||
| "webhook-deleted": "웹훅이 성공적으로 삭제되었습니다", | ||
| "webhook-delete-failed": "웹훅 삭제에 실패했습니다", | ||
| "webhook-disabled": "웹훅이 비활성화되었습니다", | ||
| "webhook-enabled": "웹훅이 활성화되었습니다", | ||
| "webhook-events": "이벤트", | ||
| "webhook-events-required": "최소 하나의 이벤트를 선택해주세요", | ||
| "webhook-name": "이름", | ||
| "webhook-name-placeholder": "내 웹훅", | ||
| "webhook-test-failed": "테스트 실패: {message}", | ||
| "webhook-test-success": "테스트 성공! 상태: {status}, 소요시간: {duration}ms", | ||
| "webhook-toggle-failed": "웹훅 전환에 실패했습니다", | ||
| "webhook-updated": "웹훅이 성공적으로 업데이트되었습니다", | ||
| "webhook-update-failed": "웹훅 업데이트에 실패했습니다", | ||
| "webhook-url": "URL", | ||
| "webhook-url-hint": "프로덕션 사용에는 HTTPS URL이 필요합니다", | ||
| "webhook-url-https-required": "웹훅 URL은 HTTPS를 사용해야 합니다", | ||
| "webhook-url-invalid": "유효한 URL을 입력해주세요", | ||
| "webhook-url-placeholder": "https://example.com/webhook", | ||
| "webhooks": "웹훅", | ||
| "webhooks-description": "조직에서 이벤트가 발생할 때 HTTP 알림을 받도록 웹훅을 구성합니다." |
There was a problem hiding this comment.
Korean webhook strings look good; consider adding the remaining Webhooks UI labels
The added webhook strings (webhook-*, webhooks, webhooks-description) are consistent and keep {message}, {status}, {duration} unchanged.
The Webhooks page also references several higher-level UI keys that do not appear in this locale, including:
add-webhook,create-first-webhook,create-webhookdelete-webhook,delete-webhook-confirm,edit-webhookno-webhooks,no-webhooks-descriptionsubscribed-events,test,view-deliveries
Those currently fall back to English. To keep ko-KR aligned with the rest of the app, it’s worth adding localized versions of these keys next to the existing webhook block.
🤖 Prompt for AI Agents
In messages/ko.json around lines 1178 to 1200, the Webhooks section is missing
several higher-level UI keys (add-webhook, create-first-webhook, create-webhook,
delete-webhook, delete-webhook-confirm, edit-webhook, no-webhooks,
no-webhooks-description, subscribed-events, test, view-deliveries) which causes
fallback to English; add these keys with concise Korean translations adjacent to
the existing webhook block, preserve any placeholders (e.g., {count}, {name})
and match tone/terminology used by existing webhook entries, and ensure JSON key
ordering/formatting stays valid.
| "zip-bundle": "Gói ứng dụng Zip", | ||
| "webhook-created": "Webhook đã được tạo thành công", | ||
| "webhook-create-failed": "Không thể tạo webhook", | ||
| "webhook-deleted": "Webhook đã được xóa thành công", | ||
| "webhook-delete-failed": "Không thể xóa webhook", | ||
| "webhook-disabled": "Webhook đã tắt", | ||
| "webhook-enabled": "Webhook đã bật", | ||
| "webhook-events": "Sự kiện", | ||
| "webhook-events-required": "Vui lòng chọn ít nhất một sự kiện", | ||
| "webhook-name": "Tên", | ||
| "webhook-name-placeholder": "Webhook của tôi", | ||
| "webhook-test-failed": "Kiểm tra thất bại: {message}", | ||
| "webhook-test-success": "Kiểm tra thành công! Trạng thái: {status}, Thời gian: {duration}ms", | ||
| "webhook-toggle-failed": "Không thể chuyển đổi webhook", | ||
| "webhook-updated": "Webhook đã được cập nhật thành công", | ||
| "webhook-update-failed": "Không thể cập nhật webhook", | ||
| "webhook-url": "URL", | ||
| "webhook-url-hint": "URL HTTPS được yêu cầu cho sử dụng sản xuất", | ||
| "webhook-url-https-required": "URL webhook phải sử dụng HTTPS", | ||
| "webhook-url-invalid": "Vui lòng nhập một URL hợp lệ", | ||
| "webhook-url-placeholder": "https://example.com/webhook", | ||
| "webhooks": "Webhooks", | ||
| "webhooks-description": "Cấu hình webhooks để nhận thông báo HTTP khi có sự kiện xảy ra trong tổ chức của bạn." |
There was a problem hiding this comment.
Webhook core strings present, but higher-level webhook UI keys missing in Vietnamese
The webhook-* and webhooks* entries are in place and look consistent, but the higher-level webhook UI keys present in other locales (e.g. messages/de.json, messages/es.json) are not defined here:
add-webhookcreate-first-webhookcreate-webhookdelete-webhookdelete-webhook-confirmedit-webhookno-webhooksno-webhooks-description
Adding these will avoid English fallbacks for buttons and empty‑state copy in the Vietnamese webhook UI.
🤖 Prompt for AI Agents
In messages/vi.json around lines 1178 to 1200, the core webhook strings exist
but several higher-level webhook UI keys are missing which causes English
fallbacks; add the following keys with appropriate Vietnamese translations:
add-webhook, create-first-webhook, create-webhook, delete-webhook,
delete-webhook-confirm, edit-webhook, no-webhooks, and no-webhooks-description,
matching the style and punctuation of neighboring entries (use concise UI
phrasing, placeholders where needed, and ensure JSON quoting/commas remain
valid).
| async function toggleWebhook(webhook: Webhook) { | ||
| if (!hasPermission.value) { | ||
| toast.error(t('no-permission')) | ||
| return | ||
| } | ||
|
|
||
| const result = await webhooksStore.toggleWebhook(webhook.id) | ||
| if (result.success) { | ||
| // After toggle, the webhook's enabled state is now the opposite | ||
| toast.success(!webhook.enabled ? t('webhook-enabled') : t('webhook-disabled')) | ||
| } | ||
| else { | ||
| toast.error(result.error || t('webhook-toggle-failed')) | ||
| } | ||
| } |
There was a problem hiding this comment.
Make toggle toast derive from a stable post‑toggle state
Right now the success toast infers the new state from !webhook.enabled:
const result = await webhooksStore.toggleWebhook(webhook.id)
if (result.success) {
toast.success(!webhook.enabled ? t('webhook-enabled') : t('webhook-disabled'))
}This works only as long as webhook.enabled still reflects the pre‑toggle value when you hit the if block. If toggleWebhook mutates the same webhook object (typical Pinia pattern), webhook.enabled will already be the new value and this inversion will show the wrong message.
Safer options:
- Capture the old state before awaiting:
const wasEnabled = webhook.enabled
const result = await webhooksStore.toggleWebhook(webhook.id)
if (result.success) {
toast.success(wasEnabled ? t('webhook-disabled') : t('webhook-enabled'))
}- Or, if
toggleWebhookreturns the new state (result.enabled/result.webhook.enabled), base the toast directly on that.
Either approach keeps the toast aligned with the actual resulting state irrespective of how the store updates its data.
🤖 Prompt for AI Agents
In src/pages/settings/organization/Webhooks.vue around lines 144 to 158, the
success toast infers the post-toggle state by negating webhook.enabled which can
be wrong if the store mutates the webhook before the await returns; capture the
prior state (e.g., const wasEnabled = webhook.enabled) before calling await
webhooksStore.toggleWebhook(webhook.id) and then use wasEnabled to decide the
toast message (wasEnabled ? t('webhook-disabled') : t('webhook-enabled')) or,
alternatively, use the new state returned by toggleWebhook (result.enabled or
result.webhook.enabled) to determine the toast so the message always reflects
the actual resulting state.
- Add webhooks function to supabase config.toml with verify_jwt=false - Fix page parameter type coercion in deliveries.ts (z.coerce.number) - Update tests to expect 'invalid_json_parse_body' for empty body 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
supabase/functions/_backend/public/webhooks/deliveries.ts (1)
13-18: Consider validating thestatusfield against allowed values.The schema accepts any string for
status, but based on the comment, only'pending','success', and'failed'are valid. Adding enum validation would provide clearer error messages.🔎 Proposed enhancement
const getDeliveriesSchema = z.object({ orgId: z.string(), webhookId: z.string(), page: z.optional(z.coerce.number()), - status: z.optional(z.string()), // 'pending', 'success', 'failed' + status: z.optional(z.enum(['pending', 'success', 'failed'])), })tests/webhooks.test.ts (2)
214-227: Test order dependency noted.Tests in this
describeblock depend oncreatedWebhookIdbeing set by the earlier[POST] /webhookstest. While this works with Vitest's default sequential execution, consider adding a comment clarifying the intended test execution order or using a shared setup fixture.
507-534: Consider adding a success path test for retry delivery.The retry delivery tests only cover error cases. A test verifying successful retry of a failed delivery would improve coverage. This could leverage the test webhook's delivery created in
[POST] /webhooks/testif it fails (as noted in the comment at line 411).Example approach:
- Get deliveries after the test webhook call
- If any delivery has status
'failed'or'pending', use it for retry test- Verify the retry returns success and the delivery is re-queued
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
supabase/config.tomlsupabase/functions/_backend/public/webhooks/deliveries.tstests/webhooks.test.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
tests/webhooks.test.tssupabase/functions/_backend/public/webhooks/deliveries.ts
tests/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Backend tests should be located in the
tests/directory and use Vitest test runner
Files:
tests/webhooks.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
tests/webhooks.test.tssupabase/functions/_backend/public/webhooks/deliveries.ts
tests/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend tests must use helpers from
tests/test-utils.tsincludinggetEndpointUrl(path)for correct worker routing andUSE_CLOUDFLARE_WORKERS=truefor CF Workers testing
Files:
tests/webhooks.test.ts
**/{migrations,tests,__tests__}/**/*.{sql,ts,js}
📄 CodeRabbit inference engine (AGENTS.md)
Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows
Files:
tests/webhooks.test.ts
supabase/functions/_backend/**
📄 CodeRabbit inference engine (CLAUDE.md)
Backend logic should be organized in
supabase/functions/_backend/with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Files:
supabase/functions/_backend/public/webhooks/deliveries.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Supabase Edge Functions use Deno runtime
Files:
supabase/functions/_backend/public/webhooks/deliveries.ts
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/public/webhooks/deliveries.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/public/webhooks/deliveries.ts
🧠 Learnings (10)
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to playwright/e2e/**/*.{ts,js} : Cover customer-facing flows with Playwright MCP suite; add scenarios under `playwright/e2e` and run them locally with `bun run test:front` before shipping UI changes
Applied to files:
tests/webhooks.test.ts
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to **/{migrations,tests,__tests__}/**/*.{sql,ts,js} : Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows
Applied to files:
tests/webhooks.test.ts
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/seed.sql : Update `supabase/seed.sql` to back new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
tests/webhooks.test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Run `bun types` after schema changes to regenerate TypeScript types from Supabase
Applied to files:
tests/webhooks.test.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Backend tests that modify local database state require a running Supabase instance
Applied to files:
tests/webhooks.test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/migrations/**/*.sql : Database migrations must be created with `supabase migration new <feature_slug>` and never modify previously committed migrations
Applied to files:
tests/webhooks.test.ts
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/migrations/*.sql : A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Applied to files:
tests/webhooks.test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
tests/webhooks.test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend files
Applied to files:
supabase/functions/_backend/public/webhooks/deliveries.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/public/webhooks/deliveries.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (6)
supabase/config.toml (1)
372-377: LGTM!The webhook function endpoint configuration follows the established pattern of other public endpoints with
verify_jwt = false(appropriate since authentication is handled via API keys in the backend) and the correctimport_mappath.supabase/functions/_backend/public/webhooks/deliveries.ts (2)
1-11: LGTM!Imports are well-organized and use the correct paths. Using
zod/minialigns with the library context for schema validation.
27-96: LGTM!The
getDeliveriesfunction is well-structured with proper validation, permission checks, and ownership verification. The previous review concerns about filter ordering and count query have been correctly addressed - the status filter is now applied before pagination, and the count query includes the status filter.tests/webhooks.test.ts (3)
1-4: LGTM!Imports are appropriate for the test file, using vitest for testing and test-utils for shared helpers as per coding guidelines.
15-49: LGTM!Good test isolation with proper setup and teardown. The cleanup order (webhooks → orgs → stripe_info) respects foreign key constraints. The localhost webhook test handles its own cleanup inline.
559-610: LGTM!The delete tests are well-structured. The double-delete test properly creates its own webhook instead of relying on shared state, ensuring test isolation.
| // Reset delivery status and queue for retry | ||
| await (supabaseAdmin(c) as any) | ||
| .from('webhook_deliveries') | ||
| .update({ | ||
| status: 'pending', | ||
| attempt_count: 0, | ||
| response_status: null, | ||
| response_body: null, | ||
| completed_at: null, | ||
| duration_ms: null, | ||
| next_retry_at: null, | ||
| }) | ||
| .eq('id', body.deliveryId) |
There was a problem hiding this comment.
Missing error handling for the update operation.
The update to reset delivery status doesn't check for errors. If the update fails, the function proceeds to queue the delivery, potentially causing inconsistent state where the delivery is queued but the database still shows the old status.
🔎 Proposed fix
// Reset delivery status and queue for retry
- await (supabaseAdmin(c) as any)
+ const { error: updateError } = await (supabaseAdmin(c) as any)
.from('webhook_deliveries')
.update({
status: 'pending',
attempt_count: 0,
response_status: null,
response_body: null,
completed_at: null,
duration_ms: null,
next_retry_at: null,
})
.eq('id', body.deliveryId)
+
+ if (updateError) {
+ throw simpleError('cannot_update_delivery', 'Cannot reset delivery for retry', { error: updateError })
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Reset delivery status and queue for retry | |
| await (supabaseAdmin(c) as any) | |
| .from('webhook_deliveries') | |
| .update({ | |
| status: 'pending', | |
| attempt_count: 0, | |
| response_status: null, | |
| response_body: null, | |
| completed_at: null, | |
| duration_ms: null, | |
| next_retry_at: null, | |
| }) | |
| .eq('id', body.deliveryId) | |
| // Reset delivery status and queue for retry | |
| const { error: updateError } = await (supabaseAdmin(c) as any) | |
| .from('webhook_deliveries') | |
| .update({ | |
| status: 'pending', | |
| attempt_count: 0, | |
| response_status: null, | |
| response_body: null, | |
| completed_at: null, | |
| duration_ms: null, | |
| next_retry_at: null, | |
| }) | |
| .eq('id', body.deliveryId) | |
| if (updateError) { | |
| throw simpleError('cannot_update_delivery', 'Cannot reset delivery for retry', { error: updateError }) | |
| } |
🤖 Prompt for AI Agents
In supabase/functions/_backend/public/webhooks/deliveries.ts around lines 132 to
144, the call that resets the delivery row does not check the Supabase response
for errors; capture the result of the awaited update (const { data, error } =
await ...), check if error is present, and if so log the error with context and
abort (throw or return an error response) so you do not continue to queue the
delivery when the DB update failed; only proceed to queue on success (no error)
and consider handling the case where no rows were updated as an error too.
- Remove enabled toggle from webhook creation form (only show when editing) - Refactor webhooks store to use direct Supabase SDK instead of edge functions - Keep edge functions only for test and retry (require server-side HTTP calls) - RLS policies handle all permissions - no need for backend CRUD endpoints 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/pages/settings/organization/Webhooks.vue (1)
147-161: Toggle toast message may show incorrect state due to mutation timing.The toast uses
!webhook.enabledto infer the new state, buttoggleWebhookcallsupdateWebhookwhich mutateswebhooks.value[index]. Sincewebhookis a reference from the reactive store array, it may already reflect the new state by the time the toast fires.Safer approach: capture the original state before the async call.
🔎 Proposed fix
async function toggleWebhook(webhook: Webhook) { if (!hasPermission.value) { toast.error(t('no-permission')) return } + const wasEnabled = webhook.enabled const result = await webhooksStore.toggleWebhook(webhook.id) if (result.success) { - // After toggle, the webhook's enabled state is now the opposite - toast.success(!webhook.enabled ? t('webhook-enabled') : t('webhook-disabled')) + toast.success(wasEnabled ? t('webhook-disabled') : t('webhook-enabled')) } else { toast.error(result.error || t('webhook-toggle-failed')) } }
🧹 Nitpick comments (5)
src/components/WebhookForm.vue (2)
36-43: Prop changes after mount won't update form fields.
onMountedonly runs once. Ifwebhookprop changes while the component is mounted, the form fields won't reflect the new values. This is fine if the parent always unmounts/remounts the form (which appears to be the case withv-if="showForm"), but consider using awatchwithimmediate: truefor robustness if that pattern changes.
124-130: Consider using DaisyUI input component for consistency.Per coding guidelines, DaisyUI components (
d-input,d-btn) are recommended for interactive elements. The current custom styling works but differs from the pattern used elsewhere.src/stores/webhooks.ts (3)
88-92: Type castingsupabase as anybypasses type safety.The
(supabase as any)cast hides potential type mismatches between the Supabase client and thewebhookstable schema. Consider generating proper types from the database schema or defining a typed interface for the Supabase client.#!/bin/bash # Check if database types are generated elsewhere in the codebase fd -e ts -x grep -l "Database\|Tables" {} \; 2>/dev/null | head -5 rg "from 'supabase'" --type ts -l | head -5
159-177: URL and event validation logic is duplicated across component and store.The same validation exists in
WebhookForm.vue(lines 51-62) and here. Consider extracting to a shared utility function for maintainability.
436-441: Optimistic update may cause state drift if server retry fails silently.The local delivery status is updated to
pendingbefore confirming the server actually queued the retry. If the edge function succeeds but the actual retry later fails, the local state won't reflect this until the next fetch. Consider refetching deliveries after a successful retry request, or document this as intentional optimistic behavior.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/WebhookForm.vuesrc/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.ts
🧰 Additional context used
📓 Path-based instructions (12)
src/stores/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Pinia stores for state management
Files:
src/stores/webhooks.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
src/stores/webhooks.ts
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}
📄 CodeRabbit inference engine (CLAUDE.md)
Mobile apps should use Capacitor with app ID
ee.forgr.capacitor_gofor native mobile functionality
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
src/**/*.{vue,ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
Konsta components are reserved for the safe area helpers; avoid importing
konstaanywhere else in the app
Files:
src/stores/webhooks.tssrc/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
src/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.vue: Use Vue 3 with Composition API and<script setup>syntax for frontend components
Style components using TailwindCSS with DaisyUI components
src/**/*.vue: Use Vue 3<script setup>syntax exclusively for all Vue component scripts
Use Tailwind utility classes for layout and spacing in Vue components
Use DaisyUI components (d-btn,d-input,d-card) for interactive elements in Vue components
Use Konsta components ONLY for safe area helpers (top/bottom insets) in Vue components; avoid other uses
UseuseRoute()fromvue-routerto access route parameters anduseRouter()for programmatic navigation in Vue componentsUse DaisyUI (
d-prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent
Files:
src/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
src/pages/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
Use file-based routing with unplugin-vue-router for frontend pages
Frontend file-based routing uses
src/pages/directory structure; routes auto-generate withunplugin-vue-routerand types are available insrc/typed-router.d.ts
Files:
src/pages/settings/organization/Webhooks.vue
src/**/*.{vue,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Files:
src/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
src/**/*.{css,scss,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Mirror the Capgo design palette from
src/styles/style.css(e.g.,--color-primary-500: #515271,--color-azure-500: #119eff) when introducing new UI, using deep slate bases with the Extract azure highlight and soft radii
Files:
src/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
src/components/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
Reusable Vue components should be organized in
src/components/directory
Files:
src/components/WebhookForm.vue
🧠 Learnings (5)
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/stores/**/*.{ts,tsx} : Use Pinia stores for state management
Applied to files:
src/stores/webhooks.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/**/*.vue : Use Vue 3 with Composition API and `<script setup>` syntax for frontend components
Applied to files:
src/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to src/**/*.{vue,css,scss} : The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-23T01:19:04.593Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:04.593Z
Learning: In the Cap-go codebase, ensure that app permission checks never include the role 'owner'. App-level permissions should be based on the user_min_right enum with values: read, upload, write, admin, super_admin (and NOT owner). This pattern applies across Vue components that perform permission checks; if you find a check referencing 'owner' for app-level access, replace it with the appropriate user_min_right value and keep organization-level owner handling in organization.ts.
Applied to files:
src/pages/settings/organization/Webhooks.vuesrc/components/WebhookForm.vue
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to src/**/*.vue : Use Vue 3 `<script setup>` syntax exclusively for all Vue component scripts
Applied to files:
src/components/WebhookForm.vue
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run tests
- GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (13)
src/components/WebhookForm.vue (3)
1-17: LGTM! Clean component setup following Vue 3 best practices.The component correctly uses Vue 3 Composition API with
<script setup>, properly typed props and emits, and imports via the~/alias as per coding guidelines.
45-62: URL validation correctly updated per previous feedback.The hostname validation now uses exact matching instead of
includes(), properly handling localhost and loopback addresses.
188-201: LGTM! Enabled toggle correctly shown only in edit mode.The toggle is properly hidden during creation (webhooks start enabled) and only shown when editing, matching the commit message requirements.
src/pages/settings/organization/Webhooks.vue (6)
1-27: LGTM! Well-structured component setup with proper store integration.Clean imports, correct use of Pinia stores with
storeToRefsfor reactive state, and proper i18n setup.
72-96: LGTM! Clean form submission handling with proper create/edit separation.The destructuring to omit
enabledon creation aligns with the backend behavior (webhooks are always enabled on creation).
98-127: LGTM! Delete confirmation follows good UX patterns.Using a confirmation dialog prevents accidental deletions, and the async handler properly awaits the store action.
188-240: LGTM! Well-designed loading, empty, and list states.The component handles all UI states appropriately with clear user feedback and accessible CTAs.
391-394: LGTM! Route configuration aligns with organization settings structure.The
settingslayout meta ensures consistent navigation with other organization settings pages.
41-43: No action needed—permission check is correct and consistent.The permission check for webhooks (
['admin', 'super_admin']) follows the established pattern across all organization settings pages (Members, Plans, index). Theownerrole is deliberately excluded from organization permission management; it is handled separately as the entity creator (viacreated_by). This design is consistent throughout the organization settings module.src/stores/webhooks.ts (4)
1-60: LGTM! Well-defined interfaces and constants.Clean TypeScript interfaces for the webhook domain model and a well-structured
WEBHOOK_EVENT_TYPESconstant withas constfor type narrowing.
450-460: LGTM! Toggle implementation is clean and leverages existing updateWebhook.The toggle correctly delegates to
updateWebhook, ensuring consistent behavior and local state updates.
462-493: LGTM! Clean store API with complete reset functionality.The store correctly exposes all necessary state and actions, and the
resetfunction properly clears all state for cleanup during organization switches or logout.
64-64: No changes required. TheuseSupabase()function is implemented as a singleton pattern that is safe to call at module level, not a Vue composable with reactivity dependencies. It synchronously returns a cachedSupabaseClientinstance after initial creation.
Users can now subscribe to app create/update/delete events via webhooks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
supabase/functions/_backend/utils/webhook.ts (1)
83-98: Timestamp inconsistency: using current time instead of audit log timestamp.Line 87 uses
new Date().toISOString()rather thanauditLogData.created_at. This creates a discrepancy between when the event actually occurred and the webhook payload timestamp.🔎 Proposed fix
export function buildWebhookPayload(auditLogData: AuditLogData): WebhookPayload { return { event: `${auditLogData.table_name}.${auditLogData.operation}`, event_id: crypto.randomUUID(), - timestamp: new Date().toISOString(), + timestamp: auditLogData.created_at, org_id: auditLogData.org_id, data: {
🧹 Nitpick comments (4)
src/stores/webhooks.ts (3)
89-93: Consider proper typing instead of(supabase as any)throughout the store.The
(supabase as any)pattern appears in multiple locations (lines 89, 124, 181, 254, 294, 381). This bypasses TypeScript's type safety. If the Supabase client types are incomplete or incorrect, consider creating a properly-typed wrapper or using type assertions with specific types instead ofany.Example approach with type assertion
// Define a minimal type for the Supabase client methods you need type SupabaseClient = { from: (table: string) => any functions: { invoke: (name: string, options: any) => Promise<any> } } // Then use type assertion once const supabase = useSupabase() as SupabaseClient
160-178: Extract duplicated validation logic into helper functions.The URL validation (lines 160-171, 230-241) and event validation (lines 174-178, 245-250) logic is duplicated between
createWebhookandupdateWebhook. This violates DRY principles and makes maintenance harder.Suggested refactor with validation helpers
// Add these helper functions before the store definition function validateWebhookUrl(url: string): { valid: boolean, error?: string } { try { const parsedUrl = new URL(url) const isLocalhost = parsedUrl.hostname === 'localhost' || parsedUrl.hostname.endsWith('.localhost') const isLoopback = parsedUrl.hostname === '127.0.0.1' || parsedUrl.hostname === '::1' if (parsedUrl.protocol !== 'https:' && !isLocalhost && !isLoopback) { return { valid: false, error: 'Webhook URL must use HTTPS' } } return { valid: true } } catch { return { valid: false, error: 'Invalid URL' } } } function validateWebhookEvents(events: string[]): { valid: boolean, error?: string } { const validEvents = WEBHOOK_EVENT_TYPES.map(e => e.value) const invalidEvents = events.filter(e => !validEvents.includes(e as any)) if (invalidEvents.length > 0) { return { valid: false, error: `Invalid event types: ${invalidEvents.join(', ')}` } } return { valid: true } }Then replace the validation blocks with:
- try { - const parsedUrl = new URL(webhookData.url) - const isLocalhost = parsedUrl.hostname === 'localhost' || parsedUrl.hostname.endsWith('.localhost') - const isLoopback = parsedUrl.hostname === '127.0.0.1' || parsedUrl.hostname === '::1' - if (parsedUrl.protocol !== 'https:' && !isLocalhost && !isLoopback) { - return { success: false, error: 'Webhook URL must use HTTPS' } - } - } - catch { - return { success: false, error: 'Invalid URL' } - } + const urlValidation = validateWebhookUrl(webhookData.url) + if (!urlValidation.valid) { + return { success: false, error: urlValidation.error } + }Also applies to: 230-250
437-442: Consider refetching delivery state instead of optimistic update.After queuing a retry, the code optimistically sets
status: 'pending'andattempt_count: 0in the local state. However, the backend might process the retry immediately or it may fail again, causing the local state to become stale. Consider refetching the delivery record after a successful retry to ensure the UI reflects the actual state.Alternative approach
if (error) { return { success: false, error: error.message || 'Failed to retry delivery' } } - // Update local delivery status - const index = deliveries.value.findIndex(d => d.id === deliveryId) - if (index !== -1) { - deliveries.value[index].status = 'pending' - deliveries.value[index].attempt_count = 0 - } + // Optionally refetch the delivery to get the actual state + // Or leave the UI as-is and let the user refresh return { success: true }supabase/functions/_backend/utils/webhook.ts (1)
260-283: Consider atomic increment to avoid potential race condition.The current implementation performs a SELECT followed by an UPDATE, which is not atomic. If called concurrently (unlikely but possible), both calls could read the same count. Use a single atomic UPDATE with SQL increment:
🔎 Proposed atomic implementation
export async function incrementAttemptCount( c: Context, deliveryId: string, ): Promise<number> { - const { data, error } = await (supabaseAdmin(c) as any) + const { data, error } = await (supabaseAdmin(c) as any) .from('webhook_deliveries') - .select('attempt_count') + .update({ attempt_count: supabaseAdmin(c).rpc('increment_column', { row_id: deliveryId, column_name: 'attempt_count' }) }) + .eq('id', deliveryId) + .select('attempt_count') + .single() - .eq('id', deliveryId) - .single() - - if (error || !data) { - cloudlogErr({ requestId: c.get('requestId'), message: 'Error getting attempt count', error: serializeError(error) }) - return 0 - } - - const newCount = data.attempt_count + 1 - - await (supabaseAdmin(c) as any) - .from('webhook_deliveries') - .update({ attempt_count: newCount }) - .eq('id', deliveryId) - return newCount + if (error || !data) { + cloudlogErr({ requestId: c.get('requestId'), message: 'Error incrementing attempt count', error: serializeError(error) }) + return 0 + } + + return data.attempt_count }Alternatively, use raw SQL via pg client:
const db = getPgClient(c) try { const result = await db.query( 'UPDATE webhook_deliveries SET attempt_count = attempt_count + 1 WHERE id = $1 RETURNING attempt_count', [deliveryId] ) return result.rows[0]?.attempt_count ?? 0 } finally { closeClient(c, db) }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/stores/webhooks.tssupabase/functions/_backend/utils/webhook.ts
🧰 Additional context used
📓 Path-based instructions (11)
src/stores/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Pinia stores for state management
Files:
src/stores/webhooks.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
src/stores/webhooks.tssupabase/functions/_backend/utils/webhook.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
src/stores/webhooks.tssupabase/functions/_backend/utils/webhook.ts
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}
📄 CodeRabbit inference engine (CLAUDE.md)
Mobile apps should use Capacitor with app ID
ee.forgr.capacitor_gofor native mobile functionality
Files:
src/stores/webhooks.ts
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/stores/webhooks.ts
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/stores/webhooks.ts
src/**/*.{vue,ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
Konsta components are reserved for the safe area helpers; avoid importing
konstaanywhere else in the app
Files:
src/stores/webhooks.ts
supabase/functions/_backend/**
📄 CodeRabbit inference engine (CLAUDE.md)
Backend logic should be organized in
supabase/functions/_backend/with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Files:
supabase/functions/_backend/utils/webhook.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Supabase Edge Functions use Deno runtime
Files:
supabase/functions/_backend/utils/webhook.ts
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/utils/webhook.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/utils/webhook.ts
🧠 Learnings (11)
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/stores/**/*.{ts,tsx} : Use Pinia stores for state management
Applied to files:
src/stores/webhooks.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use structured logging with `cloudlog({ requestId: c.get('requestId'), message: '...' })` for all backend logging
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Backend code must be placed in `supabase/functions/_backend/` as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Run `bun types` after schema changes to regenerate TypeScript types from Supabase
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend files
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/seed.sql : Update `supabase/seed.sql` to back new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Backend tests that modify local database state require a running Supabase instance
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Investigate failing Supabase tests by reviewing Docker container logs and relevant service logs before retrying
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to supabase/functions/**/*.ts : Supabase Edge Functions use Deno runtime
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
supabase/functions/_backend/utils/webhook.ts
🧬 Code graph analysis (1)
supabase/functions/_backend/utils/webhook.ts (3)
src/stores/webhooks.ts (3)
Webhook(7-17)WebhookDelivery(19-36)WEBHOOK_EVENT_TYPES(55-61)supabase/functions/_backend/utils/logging.ts (2)
serializeError(17-27)cloudlog(3-15)supabase/functions/_backend/utils/pg.ts (1)
closeClient(206-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (4)
src/stores/webhooks.ts (2)
67-494: Overall store implementation looks solid.The Pinia store follows proper patterns with well-organized state, actions, and return values. The CRUD operations are comprehensive with appropriate error handling, validation, and local state synchronization. The integration with the organization store and Supabase backend is clean.
65-65: Module-leveluseSupabase()call is safe.
useSupabase()is a factory function with singleton caching, not a Vue composable. It doesn't depend on component context, plugin injections, or composition API features—only on environment variables. The module-level initialization pattern is intentionally used throughout the codebase (stores and services) and is the correct approach.supabase/functions/_backend/utils/webhook.ts (2)
405-445: LGTM! Direct SQL approach for pgmq.send is correct.This addresses the previous concern about missing
pgmq_sendRPC by using direct SQL viagetPgClient. The try/finally pattern ensures proper client cleanup. As per coding guidelines, this correctly usesgetPgClient()fromutils/pg.tsfor PostgreSQL access.
447-500: Good resilience pattern with fallback to immediate delivery.The fallback from delayed to immediate queue on error is a sensible resilience pattern that ensures webhook deliveries aren't lost if the delay feature fails. Logging clearly indicates when fallback occurs.
| deliveries.value = data || [] | ||
| deliveryPagination.value = { | ||
| page, | ||
| per_page: DELIVERIES_PER_PAGE, | ||
| total: count ?? 0, | ||
| has_more: (data?.length ?? 0) === DELIVERIES_PER_PAGE, | ||
| } |
There was a problem hiding this comment.
Fix pagination has_more calculation.
The current logic has_more: (data?.length ?? 0) === DELIVERIES_PER_PAGE incorrectly returns true when the last page happens to have exactly DELIVERIES_PER_PAGE items. For example, with 100 total records and 50 per page, page 2 (items 50-99) will show has_more: true even though there are no more pages.
🔎 Proposed fix
deliveries.value = data || []
deliveryPagination.value = {
page,
per_page: DELIVERIES_PER_PAGE,
total: count ?? 0,
- has_more: (data?.length ?? 0) === DELIVERIES_PER_PAGE,
+ has_more: (page + 1) * DELIVERIES_PER_PAGE < (count ?? 0),
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| deliveries.value = data || [] | |
| deliveryPagination.value = { | |
| page, | |
| per_page: DELIVERIES_PER_PAGE, | |
| total: count ?? 0, | |
| has_more: (data?.length ?? 0) === DELIVERIES_PER_PAGE, | |
| } | |
| deliveries.value = data || [] | |
| deliveryPagination.value = { | |
| page, | |
| per_page: DELIVERIES_PER_PAGE, | |
| total: count ?? 0, | |
| has_more: (page + 1) * DELIVERIES_PER_PAGE < (count ?? 0), | |
| } |
🤖 Prompt for AI Agents
In src/stores/webhooks.ts around lines 400 to 406, the has_more calculation
currently compares data.length to DELIVERIES_PER_PAGE which wrongly returns true
for a full last page; change it to use the total count when available: compute
has_more as (count !== undefined && count !== null) ? (page *
DELIVERIES_PER_PAGE) < count : ((data?.length ?? 0) === DELIVERIES_PER_PAGE) so
when total is known it accurately reflects remaining pages and otherwise fall
back to the existing length check.
| export async function deliverWebhook( | ||
| c: Context, | ||
| deliveryId: string, | ||
| url: string, | ||
| payload: WebhookPayload, | ||
| ): Promise<{ success: boolean, status?: number, body?: string, duration?: number }> { | ||
| const startTime = Date.now() | ||
| const payloadString = JSON.stringify(payload) | ||
|
|
||
| const headers: Record<string, string> = { | ||
| 'Content-Type': 'application/json', | ||
| 'User-Agent': 'Capgo-Webhook/1.0', | ||
| 'X-Capgo-Event': payload.event, | ||
| 'X-Capgo-Event-ID': payload.event_id, | ||
| 'X-Capgo-Timestamp': payload.timestamp, | ||
| } | ||
|
|
||
| try { | ||
| const controller = new AbortController() | ||
| const timeoutId = setTimeout(() => controller.abort(), 10000) // 10s timeout | ||
|
|
||
| const response = await fetch(url, { | ||
| method: 'POST', | ||
| headers, | ||
| body: payloadString, | ||
| signal: controller.signal, | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find webhook-related files
fd -type f -name "*.ts" supabase/functions/_backend/ | grep -i webhook
# Search for webhook creation/update endpoints
rg -n "createWebhook|updateWebhook|POST|PUT" supabase/functions/_backend/ -A 3 -B 1 --type ts | head -100Repository: Cap-go/capgo
Length of output: 7545
🏁 Script executed:
#!/bin/bash
# Look for URL validation patterns in webhook files
rg -n "validateUrl|isValidUrl|private.*ip|localhost|SSRF|blocked.*url" supabase/functions/_backend/ --type ts
# Check webhook creation files more thoroughly
fd -type f -name "*.ts" supabase/functions/_backend/public/webhooks/ -o supabase/functions/_backend/public/ | xargs ls -la 2>/dev/null | head -20Repository: Cap-go/capgo
Length of output: 2746
🏁 Script executed:
#!/bin/bash
# Get the webhook creation (POST) handler
cat -n supabase/functions/_backend/public/webhooks/post.ts
# Get the webhook update (PUT) handler
cat -n supabase/functions/_backend/public/webhooks/put.tsRepository: Cap-go/capgo
Length of output: 6584
Webhook URL validation is incomplete for SSRF protection.
While URL validation does occur at webhook creation time (post.ts and put.ts), it only blocks localhost and loopback IPs (127.0.0.1, ::1). The validation should also block other private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), link-local addresses (169.254.0.0/16), and IPv6 private ranges (fc00::/7) to prevent SSRF attacks against internal services.
- Add secret field to webhooks table (whsec_* format)
- Generate HMAC-SHA256 signatures for webhook payloads
- Send signature in X-Capgo-Signature header (format: v1={timestamp}.{hmac})
- Send timestamp in X-Capgo-Timestamp header for replay protection
- Add UI to display and copy webhook signing secret
- Add translations for signing secret UI elements
Webhook receivers can verify requests using:
1. Extract timestamp and signature from X-Capgo-Signature header
2. Compute HMAC-SHA256 of "{timestamp}.{payload}" using the secret
3. Compare computed signature with received signature
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (4)
supabase/functions/_backend/utils/webhook.ts (1)
84-99: Timestamp inconsistency: using current time instead of audit log timestamp.Line 88 generates a new timestamp with
new Date().toISOString()rather than usingauditLogData.created_at. This creates a discrepancy between when the event actually occurred (audit log timestamp) and the webhook payload timestamp.🔎 Proposed fix
export function buildWebhookPayload(auditLogData: AuditLogData): WebhookPayload { return { event: `${auditLogData.table_name}.${auditLogData.operation}`, event_id: crypto.randomUUID(), - timestamp: new Date().toISOString(), + timestamp: auditLogData.created_at, org_id: auditLogData.org_id,supabase/functions/_backend/triggers/webhook_delivery.ts (1)
3-19: UsecreateHonoinstead of directnew Hono()instantiation.Per coding guidelines, use
createHonofromutils/hono.tsfor Hono framework initialization.🔎 Proposed fix
import type { MiddlewareKeyVariables } from '../utils/hono.ts' import type { WebhookPayload } from '../utils/webhook.ts' -import { Hono } from 'hono/tiny' -import { BRES, middlewareAPISecret } from '../utils/hono.ts' +import { BRES, createHono, middlewareAPISecret } from '../utils/hono.ts' ... -export const app = new Hono<MiddlewareKeyVariables>() +export const app = createHono()src/pages/settings/organization/Webhooks.vue (1)
148-162: Toggle toast still relies on pre-toggle state which may be stale.The current logic uses
!webhook.enabledafter the async toggle, assumingwebhook.enabledstill holds the pre-toggle value. If the store mutates the webhook object duringtoggleWebhook, this will show the wrong message.🔎 Safer approach: capture state before awaiting
async function toggleWebhook(webhook: Webhook) { if (!hasPermission.value) { toast.error(t('no-permission')) return } + const wasEnabled = webhook.enabled const result = await webhooksStore.toggleWebhook(webhook.id) if (result.success) { - // After toggle, the webhook's enabled state is now the opposite - toast.success(!webhook.enabled ? t('webhook-enabled') : t('webhook-disabled')) + toast.success(wasEnabled ? t('webhook-disabled') : t('webhook-enabled')) } else { toast.error(result.error || t('webhook-toggle-failed')) } }src/stores/webhooks.ts (1)
401-407: Fix paginationhas_morecalculation.The current logic
has_more: (data?.length ?? 0) === DELIVERIES_PER_PAGEincorrectly returnstruewhen the last page happens to have exactly 50 items.🔎 Proposed fix using total count
deliveries.value = data || [] deliveryPagination.value = { page, per_page: DELIVERIES_PER_PAGE, total: count ?? 0, - has_more: (data?.length ?? 0) === DELIVERIES_PER_PAGE, + has_more: (page + 1) * DELIVERIES_PER_PAGE < (count ?? 0), }
🧹 Nitpick comments (1)
src/pages/settings/organization/Webhooks.vue (1)
329-349: Consider masking the secret by default with a reveal toggle.The signing secret is displayed in plain text. While this is intentional for copying, consider masking it by default with a "Show/Hide" toggle to prevent accidental exposure when screen sharing.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
messages/en.jsonsrc/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.tssupabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/utils/webhook.tssupabase/migrations/20251227181040_webhook_secret.sql
🚧 Files skipped from review as they are similar to previous changes (1)
- supabase/functions/_backend/public/webhooks/test.ts
🧰 Additional context used
📓 Path-based instructions (18)
supabase/migrations/**/*.sql
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Database migrations must be created with
supabase migration new <feature_slug>and never modify previously committed migrations
Files:
supabase/migrations/20251227181040_webhook_secret.sql
**/{migrations,tests,__tests__}/**/*.{sql,ts,js}
📄 CodeRabbit inference engine (AGENTS.md)
Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows
Files:
supabase/migrations/20251227181040_webhook_secret.sql
supabase/migrations/*.sql
📄 CodeRabbit inference engine (AGENTS.md)
supabase/migrations/*.sql: When creating schema changes, usesupabase migration new <feature_slug>to create a single migration file and keep editing that file until the feature ships; never edit previously committed migrations
A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Do not create new cron jobs; instead update theprocess_all_cron_tasksfunction in a new migration file to add your job if needed
Files:
supabase/migrations/20251227181040_webhook_secret.sql
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
supabase/functions/_backend/triggers/webhook_delivery.tssrc/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.tssupabase/functions/_backend/utils/webhook.ts
supabase/functions/_backend/**
📄 CodeRabbit inference engine (CLAUDE.md)
Backend logic should be organized in
supabase/functions/_backend/with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Files:
supabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/utils/webhook.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
supabase/functions/_backend/triggers/webhook_delivery.tssrc/stores/webhooks.tssupabase/functions/_backend/utils/webhook.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Supabase Edge Functions use Deno runtime
Files:
supabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/utils/webhook.ts
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/utils/webhook.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/triggers/webhook_delivery.tssupabase/functions/_backend/utils/webhook.ts
src/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.vue: Use Vue 3 with Composition API and<script setup>syntax for frontend components
Style components using TailwindCSS with DaisyUI components
src/**/*.vue: Use Vue 3<script setup>syntax exclusively for all Vue component scripts
Use Tailwind utility classes for layout and spacing in Vue components
Use DaisyUI components (d-btn,d-input,d-card) for interactive elements in Vue components
Use Konsta components ONLY for safe area helpers (top/bottom insets) in Vue components; avoid other uses
UseuseRoute()fromvue-routerto access route parameters anduseRouter()for programmatic navigation in Vue componentsUse DaisyUI (
d-prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent
Files:
src/pages/settings/organization/Webhooks.vue
src/pages/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
Use file-based routing with unplugin-vue-router for frontend pages
Frontend file-based routing uses
src/pages/directory structure; routes auto-generate withunplugin-vue-routerand types are available insrc/typed-router.d.ts
Files:
src/pages/settings/organization/Webhooks.vue
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}
📄 CodeRabbit inference engine (CLAUDE.md)
Mobile apps should use Capacitor with app ID
ee.forgr.capacitor_gofor native mobile functionality
Files:
src/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.ts
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.ts
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.ts
src/**/*.{vue,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Files:
src/pages/settings/organization/Webhooks.vue
src/**/*.{vue,ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
Konsta components are reserved for the safe area helpers; avoid importing
konstaanywhere else in the app
Files:
src/pages/settings/organization/Webhooks.vuesrc/stores/webhooks.ts
src/**/*.{css,scss,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Mirror the Capgo design palette from
src/styles/style.css(e.g.,--color-primary-500: #515271,--color-azure-500: #119eff) when introducing new UI, using deep slate bases with the Extract azure highlight and soft radii
Files:
src/pages/settings/organization/Webhooks.vue
src/stores/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Pinia stores for state management
Files:
src/stores/webhooks.ts
🧠 Learnings (21)
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/migrations/*.sql : A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Applied to files:
supabase/migrations/20251227181040_webhook_secret.sql
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/seed.sql : Update `supabase/seed.sql` to back new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
supabase/migrations/20251227181040_webhook_secret.sqlsupabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/migrations/**/*.sql : Database migrations must be created with `supabase migration new <feature_slug>` and never modify previously committed migrations
Applied to files:
supabase/migrations/20251227181040_webhook_secret.sql
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/migrations/*.sql : When creating schema changes, use `supabase migration new <feature_slug>` to create a single migration file and keep editing that file until the feature ships; never edit previously committed migrations
Applied to files:
supabase/migrations/20251227181040_webhook_secret.sql
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/migrations/20251227181040_webhook_secret.sqlsupabase/functions/_backend/triggers/webhook_delivery.ts
📚 Learning: 2025-12-25T11:22:13.039Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:13.039Z
Learning: In SQL migrations under the repository (e.g., supabase/migrations), enforce that when an org has enforcing_2fa=true, all users (including super_admins) must have 2FA enabled before accessing any org functions, including check_org_members_2fa_enabled. Do not grant admin exceptions to 2FA requirements. This ensures consistent security enforcement across all org-related operations; implement this rule within relevant migrations and associated stored procedures/tests.
Applied to files:
supabase/migrations/20251227181040_webhook_secret.sql
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/triggers/webhook_delivery.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/triggers/webhook_delivery.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to cloudflare_workers/api/index.ts : API Worker (port 8787) routes: `/bundle`, `/app`, `/device`, `/channel`, `/private/*`, `/triggers`
Applied to files:
supabase/functions/_backend/triggers/webhook_delivery.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/**/*.vue : Use Vue 3 with Composition API and `<script setup>` syntax for frontend components
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to src/**/*.vue : Use Vue 3 `<script setup>` syntax exclusively for all Vue component scripts
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to src/**/*.{vue,css,scss} : The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-23T01:19:04.593Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:04.593Z
Learning: In the Cap-go codebase, ensure that app permission checks never include the role 'owner'. App-level permissions should be based on the user_min_right enum with values: read, upload, write, admin, super_admin (and NOT owner). This pattern applies across Vue components that perform permission checks; if you find a check referencing 'owner' for app-level access, replace it with the appropriate user_min_right value and keep organization-level owner handling in organization.ts.
Applied to files:
src/pages/settings/organization/Webhooks.vue
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/stores/**/*.{ts,tsx} : Use Pinia stores for state management
Applied to files:
src/stores/webhooks.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use structured logging with `cloudlog({ requestId: c.get('requestId'), message: '...' })` for all backend logging
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Run `bun types` after schema changes to regenerate TypeScript types from Supabase
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend files
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Backend tests that modify local database state require a running Supabase instance
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Investigate failing Supabase tests by reviewing Docker container logs and relevant service logs before retrying
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to supabase/functions/**/*.ts : Supabase Edge Functions use Deno runtime
Applied to files:
supabase/functions/_backend/utils/webhook.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
supabase/functions/_backend/utils/webhook.ts
🧬 Code graph analysis (2)
src/stores/webhooks.ts (4)
supabase/functions/_backend/utils/webhook.ts (2)
Webhook(37-48)WebhookDelivery(51-68)src/services/supabase.ts (1)
useSupabase(58-71)src/stores/organization.ts (1)
useOrganizationStore(19-317)supabase/functions/_backend/public/webhooks/delete.ts (1)
deleteWebhook(13-52)
supabase/functions/_backend/utils/webhook.ts (3)
src/stores/webhooks.ts (1)
Webhook(7-18)supabase/functions/_backend/utils/logging.ts (2)
serializeError(17-27)cloudlog(3-15)supabase/functions/_backend/utils/pg.ts (1)
closeClient(206-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run tests
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (11)
supabase/migrations/20251227181040_webhook_secret.sql (2)
1-19: Migration structure follows best practices.The pattern of adding nullable column, populating existing rows, then setting NOT NULL is correct. The
IF NOT EXISTSensures idempotency.
15-16: The DEFAULT expression in this migration is correct. In PostgreSQL,gen_random_uuid()is marked VOLATILE, which means DEFAULT expressions containing it are re-evaluated at INSERT time for each row—not evaluated once at ALTER TABLE time. Each new webhook will receive a unique secret.The migration file follows proper structure: add column (nullable) → populate existing rows → set NOT NULL → set DEFAULT. No changes needed.
Likely an incorrect or invalid review comment.
supabase/functions/_backend/utils/webhook.ts (4)
104-123: LGTM - Appropriate error handling and logging.The function correctly handles errors by returning an empty array (safe default) and uses structured logging per coding guidelines.
163-190: HMAC-SHA256 signature implementation is correct.The signature generation uses standard Web Crypto APIs, compatible with Deno and Cloudflare Workers runtimes. The format
v1={timestamp}.{hmac}allows for future versioning.
195-269: Webhook delivery implementation is solid with appropriate safeguards.The function includes:
- 10-second timeout preventing hanging connections
- Response body truncation (10KB limit) preventing memory issues
- Proper error handling with structured logging
- AbortController for clean timeout handling
The SSRF concern noted in past reviews relates to URL validation at webhook creation time (post.ts/put.ts), not this delivery function.
449-541: Queue functions correctly use direct SQL via pg client.The implementation uses
pgmq.senddirectly via the pg client, avoiding the RPC issue noted in previous reviews. The fallback mechanism inqueueWebhookDeliveryWithDelayprovides good resilience, andcloseClientis properly called in thefinallyblock.supabase/functions/_backend/triggers/webhook_delivery.ts (1)
40-205: Handler flow is well-structured with appropriate safeguards.The implementation includes:
- Idempotency check (skips already-successful deliveries)
- Exponential backoff for retries (2min, 4min, 8min)
- Notification on permanent failure with rate limiting
- Comprehensive error handling and logging
Minor: The
if (webhook)check at line 168 is redundant sincewebhookwas already validated at lines 86-94 and would have returned early if null.src/pages/settings/organization/Webhooks.vue (1)
1-50: Component follows Vue 3 and project guidelines.Uses
<script setup>syntax, Composition API, Pinia stores, and Tailwind CSS as required by coding guidelines. Permission checks are properly implemented usinghasPermissionsInRole.src/stores/webhooks.ts (2)
161-172: Client-side URL validation is appropriate.The HTTPS requirement with localhost exceptions for development is reasonable. Note that complete SSRF protection (blocking private IP ranges) should be enforced server-side in the backend handlers.
66-68: Supabase client initialization at module level is acceptable.The
useSupabase()function returns a singleton, so calling it at module level is safe and ensures consistent client usage.messages/en.json (1)
1155-1205: Webhook translations are comprehensive and well-formatted.The new keys cover all webhook UI elements with consistent placeholder syntax (
{name},{status},{duration}) and clear, action-oriented messaging.
| "warning-paid-subscriptions-message": "The following organizations have active paid subscriptions. These subscriptions will be cancelled immediately:", | ||
| "add-webhook": "Add Webhook", | ||
| "completed-at": "Completed at", | ||
| "create": "Create", |
There was a problem hiding this comment.
Duplicate "create" key.
Line 1157 adds "create": "Create" which duplicates line 469. JSON allows this but the later value wins, and it creates maintenance confusion.
🔎 Remove duplicate
"completed-at": "Completed at",
- "create": "Create",
"create-first-webhook": "Create your first webhook",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "create": "Create", | |
| "completed-at": "Completed at", | |
| "create-first-webhook": "Create your first webhook", |
🤖 Prompt for AI Agents
In messages/en.json around line 1157 (and duplicate at line 469), there is a
duplicate "create" key; remove the redundant entry at line 1157 so the file
keeps a single authoritative "create" translation (ensure no other duplicates
exist), then run a JSON lint/parse to confirm the file remains valid.
| // Update local delivery status | ||
| const index = deliveries.value.findIndex(d => d.id === deliveryId) | ||
| if (index !== -1) { | ||
| deliveries.value[index].status = 'pending' | ||
| deliveries.value[index].attempt_count = 0 | ||
| } | ||
|
|
There was a problem hiding this comment.
Incorrect optimistic update: attempt_count should not be reset to 0.
When retrying a delivery, the attempt count should not be reset. The backend will increment it. Setting it to 0 shows incorrect state in the UI.
🔎 Proposed fix
// Update local delivery status
const index = deliveries.value.findIndex(d => d.id === deliveryId)
if (index !== -1) {
deliveries.value[index].status = 'pending'
- deliveries.value[index].attempt_count = 0
+ // Don't reset attempt_count - the backend will increment it
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Update local delivery status | |
| const index = deliveries.value.findIndex(d => d.id === deliveryId) | |
| if (index !== -1) { | |
| deliveries.value[index].status = 'pending' | |
| deliveries.value[index].attempt_count = 0 | |
| } | |
| // Update local delivery status | |
| const index = deliveries.value.findIndex(d => d.id === deliveryId) | |
| if (index !== -1) { | |
| deliveries.value[index].status = 'pending' | |
| // Don't reset attempt_count - the backend will increment it | |
| } |
🤖 Prompt for AI Agents
In src/stores/webhooks.ts around lines 438 to 444, the optimistic update
incorrectly resets deliveries.value[index].attempt_count to 0 when retrying;
remove that assignment and only set deliveries.value[index].status = 'pending'
so the UI reflects the correct attempt count (the backend will increment it).
| export async function incrementAttemptCount( | ||
| c: Context, | ||
| deliveryId: string, | ||
| ): Promise<number> { | ||
| const { data, error } = await (supabaseAdmin(c) as any) | ||
| .from('webhook_deliveries') | ||
| .select('attempt_count') | ||
| .eq('id', deliveryId) | ||
| .single() | ||
|
|
||
| if (error || !data) { | ||
| cloudlogErr({ requestId: c.get('requestId'), message: 'Error getting attempt count', error: serializeError(error) }) | ||
| return 0 | ||
| } | ||
|
|
||
| const newCount = data.attempt_count + 1 | ||
|
|
||
| await (supabaseAdmin(c) as any) | ||
| .from('webhook_deliveries') | ||
| .update({ attempt_count: newCount }) | ||
| .eq('id', deliveryId) | ||
|
|
||
| return newCount | ||
| } |
There was a problem hiding this comment.
Race condition in attempt count increment.
The SELECT-then-UPDATE pattern is non-atomic. If two delivery handlers process the same delivery concurrently, both could read the same attempt_count and increment to the same value, losing an increment.
🔎 Proposed atomic fix
export async function incrementAttemptCount(
c: Context,
deliveryId: string,
): Promise<number> {
- const { data, error } = await (supabaseAdmin(c) as any)
+ // Use RPC or raw SQL for atomic increment
+ const { data, error } = await (supabaseAdmin(c) as any)
+ .rpc('increment_delivery_attempt', { p_delivery_id: deliveryId })
+
+ // Or use UPDATE with RETURNING in a single query via pg client
+ // UPDATE webhook_deliveries
+ // SET attempt_count = attempt_count + 1
+ // WHERE id = $1
+ // RETURNING attempt_count
- .from('webhook_deliveries')
- .select('attempt_count')
- .eq('id', deliveryId)
- .single()
-
- if (error || !data) {
- cloudlogErr({ requestId: c.get('requestId'), message: 'Error getting attempt count', error: serializeError(error) })
- return 0
- }
-
- const newCount = data.attempt_count + 1
-
- await (supabaseAdmin(c) as any)
- .from('webhook_deliveries')
- .update({ attempt_count: newCount })
- .eq('id', deliveryId)
- return newCount
+ if (error) {
+ cloudlogErr({ requestId: c.get('requestId'), message: 'Error incrementing attempt count', error: serializeError(error) })
+ return 0
+ }
+ return data
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In supabase/functions/_backend/utils/webhook.ts around lines 301-324, the
current SELECT-then-UPDATE creates a race condition when incrementing
attempt_count; replace it with a single atomic DB update that increments the
column and returns the new value (e.g., use the client’s atomic increment
operation or an UPDATE ... SET attempt_count = attempt_count + 1 RETURNING
attempt_count), check for errors from that single call and log/return 0 on
failure, and otherwise return the returned attempt_count; ensure you still
include requestId in logs and handle the call as a single transaction-like
operation so concurrent handlers cannot lose increments.
Consolidate webhook_secret.sql into the main webhooks.sql migration: - Add secret column directly in CREATE TABLE statement - Remove separate migration file for cleaner PR 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Set immutable search_path on webhook functions to prevent schema injection: - trigger_webhook_on_audit_log - update_webhook_updated_at - cleanup_webhook_deliveries 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
supabase/migrations/20251226133417_webhooks.sql (1)
309-316: Overly permissive grants toanonrole remain unaddressed.As flagged in a previous review, granting
ALLto theanonrole on webhook tables is unusual and risky. While RLS policies don't grantanonany access (policies only targetauthenticatedandservice_role), the excessive grants violate the principle of least privilege.🔎 Suggested fix
-- Webhooks table grants -GRANT ALL ON TABLE "public"."webhooks" TO "anon"; GRANT ALL ON TABLE "public"."webhooks" TO "authenticated"; GRANT ALL ON TABLE "public"."webhooks" TO "service_role"; -- Webhook deliveries table grants -GRANT ALL ON TABLE "public"."webhook_deliveries" TO "anon"; GRANT ALL ON TABLE "public"."webhook_deliveries" TO "authenticated"; GRANT ALL ON TABLE "public"."webhook_deliveries" TO "service_role";
🧹 Nitpick comments (3)
supabase/migrations/20251226133417_webhooks.sql (3)
8-29: Table structure looks solid.The
webhookstable design is well-thought-out with appropriate foreign keys, cascade behaviors, and indexes. The partial index on(org_id, enabled) WHERE enabled = trueis efficient for querying active webhooks.Consider adding a CHECK constraint on the
eventsarray to validate allowed values at the database level, preventing invalid event types from being stored:CONSTRAINT "webhooks_events_check" CHECK ( events <@ ARRAY['apps', 'app_versions', 'channels', 'org_users', 'orgs']::TEXT[] )This is optional if validation is enforced at the API layer.
35-60: Delivery tracking table is well-designed.The denormalized
org_idcolumn enables efficient RLS without joins. The partial index on pending retries is excellent for the retry worker.Minor suggestion: add a CHECK constraint on
statusto ensure data integrity:CONSTRAINT "webhook_deliveries_status_check" CHECK (status IN ('pending', 'success', 'failed'))
292-302: Cleanup function is functional.The 7-day retention with daily cleanup is reasonable. For high-volume webhook systems, consider adding batch deletion to avoid long-running transactions:
-- Optional: batch delete to avoid lock contention DELETE FROM "public"."webhook_deliveries" WHERE "id" IN ( SELECT "id" FROM "public"."webhook_deliveries" WHERE "created_at" < NOW() - INTERVAL '7 days' LIMIT 10000 );This is optional unless you expect very high delivery volumes.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
supabase/migrations/20251226133417_webhooks.sql
🧰 Additional context used
📓 Path-based instructions (3)
supabase/migrations/**/*.sql
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Database migrations must be created with
supabase migration new <feature_slug>and never modify previously committed migrations
Files:
supabase/migrations/20251226133417_webhooks.sql
**/{migrations,tests,__tests__}/**/*.{sql,ts,js}
📄 CodeRabbit inference engine (AGENTS.md)
Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows
Files:
supabase/migrations/20251226133417_webhooks.sql
supabase/migrations/*.sql
📄 CodeRabbit inference engine (AGENTS.md)
supabase/migrations/*.sql: When creating schema changes, usesupabase migration new <feature_slug>to create a single migration file and keep editing that file until the feature ships; never edit previously committed migrations
A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Do not create new cron jobs; instead update theprocess_all_cron_tasksfunction in a new migration file to add your job if needed
Files:
supabase/migrations/20251226133417_webhooks.sql
🧠 Learnings (3)
📚 Learning: 2025-12-27T03:51:23.557Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.557Z
Learning: Applies to supabase/migrations/*.sql : Do not create new cron jobs; instead update the `process_all_cron_tasks` function in a new migration file to add your job if needed
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
📚 Learning: 2025-12-24T14:11:10.256Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539
Timestamp: 2025-12-24T14:11:10.256Z
Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
📚 Learning: 2025-12-25T11:22:13.039Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:13.039Z
Learning: In SQL migrations under the repository (e.g., supabase/migrations), enforce that when an org has enforcing_2fa=true, all users (including super_admins) must have 2FA enabled before accessing any org functions, including check_org_members_2fa_enabled. Do not grant admin exceptions to 2FA requirements. This ensures consistent security enforcement across all org-related operations; implement this rule within relevant migrations and associated stored procedures/tests.
Applied to files:
supabase/migrations/20251226133417_webhooks.sql
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (5)
supabase/migrations/20251226133417_webhooks.sql (5)
62-208: RLS policies are correctly structured.The policies appropriately separate read access (org members) from write access (admin/super_admin). Using
(SELECT "public"."get_identity"())ensures single evaluation of the identity lookup. The intentional omission of a DELETE policy forwebhook_deliveriesis correct since cleanup is handled by the cron job.
219-248: Trigger function is correctly secured.The function properly uses
SECURITY DEFINERwithSET search_path = ''to prevent search path injection attacks. Thepgmq.sendcall is schema-qualified, and the payload structure includes all necessary audit log fields for downstream processing.
273-287: LGTM!Simple and correct
updated_atauto-update pattern with propersearch_pathsecurity.
340-347: Webhook queues properly integrated into cron processing.The
webhook_dispatcherandwebhook_deliveryqueues are correctly added to the high-frequency processing (every 10 seconds), andcleanup_webhook_deliveries()is scheduled at midnight. This resolves the previously flagged concern about webhook queues not being consumed.Also applies to: 437-442
254-268: The migration ordering is correct: theaudit_logstable migration (20251226125240) precedes this webhooks migration (20251226133417), so theaudit_logstable will exist when the trigger is created. The conditional check is safe defensive programming.
| SELECT pgmq.create('webhook_dispatcher'); | ||
| SELECT pgmq.create('webhook_delivery'); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
pgmq postgres message queue create function idempotent behavior
💡 Result:
Short answer: pgmq.create is not documented as automatically idempotent — calling it when the queue/table already exists will raise an error. Use a presence check or catch the error and skip. See the pgmq functions docs. [1][2]
Example (safe/idempotent wrapper):
DO $$
BEGIN
IF to_regclass('q_my_queue') IS NULL THEN
PERFORM pgmq.create('my_queue');
END IF;
END;
$$;
References:
- PGMQ functions (create/create_partitioned). [1]
- PGMQ README / docs. [2]
🏁 Script executed:
cat supabase/migrations/20251226133417_webhooks.sql | head -220 | tail -20Repository: Cap-go/capgo
Length of output: 769
Wrap pgmq.create calls in an idempotency check.
pgmq.create raises an error if the queue already exists—it is not idempotent. Wrap each queue creation in a DO block with a to_regclass() check to prevent migration failures on re-runs:
DO $$
BEGIN
IF to_regclass('q_webhook_dispatcher') IS NULL THEN
PERFORM pgmq.create('webhook_dispatcher');
END IF;
END;
$$;
DO $$
BEGIN
IF to_regclass('q_webhook_delivery') IS NULL THEN
PERFORM pgmq.create('webhook_delivery');
END IF;
END;
$$;🤖 Prompt for AI Agents
In supabase/migrations/20251226133417_webhooks.sql around lines 213 to 214, the
direct calls to pgmq.create('webhook_dispatcher') and
pgmq.create('webhook_delivery') are not idempotent and will error if the queues
already exist; wrap each creation in a DO block that checks
to_regclass('q_<queue_name>') IS NULL before calling pgmq.create (use PERFORM
inside the DO block) so the migration can be safely re-run without failing.
The web UI calls edge functions via supabase.functions.invoke() which sends a JWT token from the logged-in user's session. The webhook test and retry endpoints were using middlewareKey which only accepts API keys, causing auth failures when testing from the UI. Changed /test and /deliveries/retry endpoints to use middlewareV2 which accepts both JWT tokens and API keys. Added checkWebhookPermissionV2 function that works with the unified AuthInfo type. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
supabase/functions/_backend/public/webhooks/deliveries.ts (1)
134-145: Missing error handling for the database update.This issue was flagged in a previous review but remains unaddressed. If the update fails, the function proceeds to queue the delivery, potentially causing inconsistent state where the delivery is queued but the database still shows the old status or error details.
🔎 Proposed fix
// Reset delivery status and queue for retry - await (supabaseAdmin(c) as any) + const { error: updateError } = await (supabaseAdmin(c) as any) .from('webhook_deliveries') .update({ status: 'pending', attempt_count: 0, response_status: null, response_body: null, completed_at: null, duration_ms: null, next_retry_at: null, }) .eq('id', body.deliveryId) + + if (updateError) { + throw simpleError('cannot_update_delivery', 'Cannot reset delivery for retry', { error: updateError }) + }
🧹 Nitpick comments (2)
supabase/functions/_backend/public/webhooks/test.ts (2)
65-78: Consider combining the two database updates.Lines 65-72 update the delivery record via
updateDeliveryResult, and lines 75-78 perform a separate update forattempt_count. This results in two round trips to the database. Consider refactoringupdateDeliveryResultto accept an optionalattemptCountparameter so both updates can be done in a single query.💡 Conceptual refactor to reduce database round trips
Modify the
updateDeliveryResultutility signature to accept attempt count:export async function updateDeliveryResult( c: Context, deliveryId: string, success: boolean, responseStatus: number | null, responseBody: string | null, duration: number, attemptCount?: number, // Add optional parameter ): Promise<void>Then update the function to conditionally include
attempt_countin the update object, and update the call site here:await updateDeliveryResult( c, delivery.id, result.success, result.status || null, result.body || null, result.duration || 0, + 1, // attempt count ) - // Update attempt count - await (supabaseAdmin(c) as any) - .from('webhook_deliveries') - .update({ attempt_count: 1 }) - .eq('id', delivery.id)
19-90: Consider adding structured logging for better observability.The function performs critical operations (test webhook delivery) but doesn't emit any logs. While the utility functions (
deliverWebhook, etc.) do log, adding entry/exit logs here would improve traceability for debugging test webhook flows.💡 Suggested logging additions
export async function test(c: Context, bodyRaw: any, auth: AuthInfo): Promise<Response> { + cloudlog({ + requestId: c.get('requestId'), + message: 'Testing webhook', + webhookId: bodyRaw?.webhookId, + orgId: bodyRaw?.orgId, + }) + const bodyParsed = bodySchema.safeParse(bodyRaw) // ... existing code ... return c.json({ success: result.success, status: result.status, duration_ms: result.duration, response_preview: result.body?.slice(0, 500), delivery_id: delivery.id, message: result.success ? 'Test webhook delivered successfully' : 'Test webhook delivery failed', }) }Import
cloudlogfrom../../utils/log.tsor wherever the logging utility is exported.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
supabase/functions/_backend/public/webhooks/deliveries.tssupabase/functions/_backend/public/webhooks/index.tssupabase/functions/_backend/public/webhooks/test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- supabase/functions/_backend/public/webhooks/index.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.ts
supabase/functions/_backend/**
📄 CodeRabbit inference engine (CLAUDE.md)
Backend logic should be organized in
supabase/functions/_backend/with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Supabase Edge Functions use Deno runtime
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.ts
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.ts
🧠 Learnings (5)
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
supabase/functions/_backend/public/webhooks/test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Backend code must be placed in `supabase/functions/_backend/` as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Applied to files:
supabase/functions/_backend/public/webhooks/test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/public/webhooks/test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/public/webhooks/test.tssupabase/functions/_backend/public/webhooks/deliveries.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend files
Applied to files:
supabase/functions/_backend/public/webhooks/deliveries.ts
🧬 Code graph analysis (2)
supabase/functions/_backend/public/webhooks/test.ts (2)
supabase/functions/_backend/utils/hono.ts (2)
AuthInfo(20-25)simpleError(247-249)supabase/functions/_backend/public/webhooks/index.ts (1)
checkWebhookPermissionV2(37-53)
supabase/functions/_backend/public/webhooks/deliveries.ts (4)
supabase/functions/_backend/utils/supabase.types.ts (1)
Database(9-2811)supabase/functions/_backend/utils/hono.ts (2)
simpleError(247-249)AuthInfo(20-25)supabase/functions/_backend/public/webhooks/index.ts (2)
checkWebhookPermission(20-31)checkWebhookPermissionV2(37-53)supabase/functions/_backend/utils/webhook.ts (3)
getDeliveryById(405-421)getWebhookById(384-400)queueWebhookDelivery(449-486)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (4)
supabase/functions/_backend/public/webhooks/deliveries.ts (1)
1-26: LGTM! Clean imports and schema definitions.The imports are well-organized, schemas use Zod 4 mini correctly, and the code follows the @antfu/eslint-config style guide (single quotes, no semicolons).
supabase/functions/_backend/public/webhooks/test.ts (3)
1-17: LGTM! Clean imports and schema definition.The imports are well-organized, follow the style guidelines (single quotes, no semicolons), and the body schema appropriately validates required fields.
19-26: LGTM! Proper validation and permission checks.The function correctly validates input with Zod's
safeParseand performs permission checks usingcheckWebhookPermissionV2, which appropriately handles both JWT and API key authentication as mentioned in the PR objectives.
28-42: LGTM! Thorough webhook validation with defense-in-depth.The code properly verifies webhook existence and ownership. The additional check at lines 40-42 provides defense-in-depth security, even though
checkWebhookPermissionV2already validated org access. The type assertion with explanatory comment is acceptable for the current state.
| const { data: webhook, error: fetchError } = await (supabaseAdmin(c) as any) | ||
| .from('webhooks') | ||
| .select('*') | ||
| .eq('id', body.webhookId) | ||
| .single() |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Get the full test.ts file and surrounding context
head -100 supabase/functions/_backend/public/webhooks/test.tsRepository: Cap-go/capgo
Length of output: 2637
🏁 Script executed:
# Check for getDrizzleClient vs supabaseAdmin patterns across backend
echo "=== getDrizzleClient usage in backend ==="
rg -n 'getDrizzleClient' supabase/functions/_backend/ --type ts --type js
echo ""
echo "=== supabaseAdmin usage in backend (excluding webhooks) ==="
rg -n 'supabaseAdmin' supabase/functions/_backend/ -g '!**/webhooks/**' --type ts --type jsRepository: Cap-go/capgo
Length of output: 38771
🏁 Script executed:
# Check the actual lines 30-34 and 75-78 in test.ts
echo "=== Lines 25-40 of test.ts ==="
sed -n '25,40p' supabase/functions/_backend/public/webhooks/test.ts
echo ""
echo "=== Lines 70-85 of test.ts ==="
sed -n '70,85p' supabase/functions/_backend/public/webhooks/test.tsRepository: Cap-go/capgo
Length of output: 1001
🏁 Script executed:
# Look for webhook utility files that use supabaseAdmin
echo "=== Webhook-related files ==="
find supabase/functions/_backend -path '*webhook*' -name '*.ts' -o -path '*webhook*' -name '*.js'
echo ""
echo "=== Search for supabaseAdmin definition ==="
rg -n 'export.*supabaseAdmin|function supabaseAdmin' supabase/functions/_backend/Repository: Cap-go/capgo
Length of output: 755
Migrate database operations from supabaseAdmin(c) to getDrizzleClient().
Lines 30-34 and 75-78 use supabaseAdmin(c) for database queries, but the coding guidelines require: "All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1." Additionally, the webhook utilities (createDeliveryRecord, updateDeliveryResult, etc. in utils/webhook.ts) also use supabaseAdmin and should be migrated to Drizzle ORM patterns.
🤖 Prompt for AI Agents
In supabase/functions/_backend/public/webhooks/test.ts around lines 30-34 (and
similarly lines 75-78), database queries currently call supabaseAdmin(c);
replace these with getDrizzleClient() from utils/pg.ts and rewrite the queries
using Drizzle ORM query builders (select from webhooks where id = body.webhookId
and fetch single row using Drizzle's API). Also update any calls to webhook
utilities (createDeliveryRecord, updateDeliveryResult, etc.) so those helpers no
longer use supabaseAdmin but instead accept or create a Drizzle client and
perform equivalent insert/update/select operations via Drizzle; ensure
parameterized values, proper transaction usage if needed, and error handling
consistent with existing behavior.
Update webhook endpoint handlers to use Context<MiddlewareKeyVariables, any, any> instead of plain Context for proper type safety and consistency with coding guidelines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The previous logic (data?.length === DELIVERIES_PER_PAGE) incorrectly returned has_more: true when total items was an exact multiple of the page size. Now uses count-based calculation to accurately determine if more pages exist. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|



Summary
Implement a complete webhooks system for Capgo allowing organizations to receive HTTP notifications for app version, channel, org member, and organization changes. The system includes database schema, backend edge functions for CRUD and delivery, frontend UI components, and comprehensive tests.
Test plan
supabase start && supabase db resetbun test:backend(webhooks.test.ts)bun serve:devChecklist
bun lint:backend && bun lint🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.