Skip to content

feat: Implement webhooks system for organization events#1303

Merged
riderx merged 22 commits intomainfrom
riderx/webhooks-system
Dec 28, 2025
Merged

feat: Implement webhooks system for organization events#1303
riderx merged 22 commits intomainfrom
riderx/webhooks-system

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Dec 26, 2025

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

  1. Start local Supabase: supabase start && supabase db reset
  2. Run tests: bun test:backend (webhooks.test.ts)
  3. Start dev server: bun serve:dev
  4. Navigate to Settings > Organization > Webhooks
  5. Create a webhook with test URL
  6. Test delivery via the "Test" button and verify delivery logs

Checklist

  • Code follows style guidelines and passes bun lint:backend && bun lint
  • Database migrations included with schema and triggers
  • Backend functions tested with comprehensive test suite
  • Frontend UI implemented with form and delivery logs
  • All changes have adequate test coverage

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Full webhook management in Organization settings: create, edit, delete, enable/disable, test, and copy signing secrets
    • Subscribe webhooks to specific events, view delivery history with request/response details, retry deliveries, and see delivery status/duration
    • Delivery logs modal with filtering, pagination, per-delivery expand/view, and UI for testing/toggling webhooks
  • Tests

    • End-to-end test coverage for webhook lifecycle, deliveries, retry and error paths
  • Chores

    • Multi-language translations and config updated to support the webhook UI and labels

✏️ Tip: You can customize this high-level summary in your review settings.

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>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 26, 2025

Note

Other AI code review bot(s) detected

CodeRabbit 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.

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Frontend components
src/components/WebhookForm.vue, src/components/WebhookDeliveryLog.vue
New modal form and delivery-log UI for creating/editing webhooks, URL validation, viewing deliveries (pagination/status), retrying, and showing payload/response.
Settings page
src/pages/settings/organization/Webhooks.vue
New organization settings page: list webhooks, add/edit/delete, test, enable/disable, view deliveries, copy secret, and permission gating.
State store
src/stores/webhooks.ts
New Pinia store: types (Webhook, WebhookDelivery), CRUD, fetchDeliveries, retryDelivery, testWebhook, toggleWebhook, validations, and Supabase integration.
Navigation
src/constants/organizationTabs.ts
Adds webhooks tab to organization settings with icon import.
Backend REST handlers
supabase/functions/_backend/public/webhooks/index.ts, .../get.ts, .../post.ts, .../put.ts, .../delete.ts, .../test.ts, .../deliveries.ts
Router + handlers for listing/getting, creating, updating, deleting, testing, deliveries listing and retry; includes permission checks and validation.
Trigger apps
supabase/functions/_backend/triggers/webhook_dispatcher.ts, supabase/functions/_backend/triggers/webhook_delivery.ts, supabase/functions/triggers/index.ts
Dispatcher enqueues deliveries from audit_log events; delivery worker processes queue with retries, backoff, failure handling; routes registered on triggers app.
Webhook utilities
supabase/functions/_backend/utils/webhook.ts
Helpers: payload builder, find/create delivery records, signature generation, deliverWebhook (HTTP + signature), queueing, retry scheduling, updating results, test payloads.
Function wiring
supabase/functions/webhooks/index.ts, supabase/functions/_backend/public/webhooks/*
Exposes webhooks function endpoint with versioning and global error wrapper; mounts backend app.
Database migration
supabase/migrations/20251226133417_webhooks.sql
Adds webhooks and webhook_deliveries tables, RLS policies, indexes, pgmq queues, triggers (audit_log enqueue, updated_at), cleanup, and cron/task integration.
Config
supabase/config.toml
Adds [functions.webhooks] block (verify_jwt=false, import_map).
Tests
tests/webhooks.test.ts
End-to-end test suite covering CRUD, validation, test delivery, deliveries listing/retry, and error paths.
Internationalization
messages/{en,de,es,fr,hi,id,it,ja,ko,pl,pt-br,ru,tr,vi,zh-cn}.json
Adds comprehensive webhook translation keys across many locales.

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
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐇 I stitched a bell for every call,

Events now hop and never fall;
Secrets copied, retries take flight,
Queues hum softly through the night,
Webhooks bloom — a rabbit's delight.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.79% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the primary change: implementing a webhooks system for organization events.
Description check ✅ Passed The description includes a summary of the implementation, detailed test plan with steps, and a mostly complete checklist, though the PR description provided is conversational rather than rigidly following the template structure.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/webhooks-system

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c09302 and 8121fae.

📒 Files selected for processing (3)
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/public/webhooks/test.ts
🚧 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 (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/deliveries.ts
  • supabase/functions/_backend/public/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/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.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/public/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 in supabase/functions/_backend/ as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
supabase/functions/**/*.{ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Backend ESLint must pass before commit; run bun lint:backend for backend files

Files:

  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
🧠 Learnings (9)
📚 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
  • 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} : 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
  • 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} : 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/deliveries.ts
  • 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} : 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-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.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/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-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.ts
🧬 Code graph analysis (1)
supabase/functions/_backend/public/webhooks/deliveries.ts (3)
supabase/functions/_backend/utils/hono.ts (3)
  • MiddlewareKeyVariables (27-41)
  • 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

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +422 to +425
const { error } = await supabaseAdmin(c).rpc('pgmq_send' as any, {
queue_name: 'webhook_delivery',
message,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +316 to +320
-- =====================================================
-- 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

riderx and others added 3 commits December 26, 2025 19:02
- 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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_TYPES have 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.ts

would 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 createdWebhookId is set or after webhook creation, the afterAll cleanup will skip deleting the webhook (line 42-44 only runs if createdWebhookId is 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 status column 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4b7275a and cf6d0fd.

📒 Files selected for processing (20)
  • messages/en.json
  • src/components/WebhookDeliveryLog.vue
  • src/components/WebhookForm.vue
  • src/constants/organizationTabs.ts
  • src/pages/settings/organization/Webhooks.vue
  • src/stores/webhooks.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/functions/triggers/index.ts
  • supabase/functions/webhooks/index.ts
  • supabase/migrations/20251226133417_webhooks.sql
  • tests/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.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • tests/webhooks.test.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/triggers/index.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • src/pages/settings/organization/Webhooks.vue
  • supabase/functions/_backend/public/webhooks/put.ts
  • supabase/functions/_backend/utils/webhook.ts
  • src/stores/webhooks.ts
  • src/components/WebhookForm.vue
  • src/components/WebhookDeliveryLog.vue
  • src/constants/organizationTabs.ts
  • supabase/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.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • supabase/functions/_backend/utils/webhook.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • tests/webhooks.test.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/triggers/index.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • supabase/functions/_backend/utils/webhook.ts
  • src/stores/webhooks.ts
  • src/constants/organizationTabs.ts
  • supabase/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.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/triggers/index.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/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 in supabase/functions/_backend/ as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • 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:backend for backend files

Files:

  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/triggers/index.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/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.ts including getEndpointUrl(path) for correct worker routing and USE_CLOUDFLARE_WORKERS=true for 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 importing konsta anywhere 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
Use useRoute() from vue-router to access route parameters and useRouter() for programmatic navigation in Vue components

Files:

  • src/pages/settings/organization/Webhooks.vue
  • src/components/WebhookForm.vue
  • src/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 with unplugin-vue-router and types are available in src/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_go for native mobile functionality

Files:

  • src/pages/settings/organization/Webhooks.vue
  • src/stores/webhooks.ts
  • src/components/WebhookForm.vue
  • src/components/WebhookDeliveryLog.vue
  • src/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 from src/styles/style.css when introducing new UI

Files:

  • src/pages/settings/organization/Webhooks.vue
  • src/components/WebhookForm.vue
  • src/components/WebhookDeliveryLog.vue
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ directory in frontend TypeScript and Vue components

Files:

  • src/pages/settings/organization/Webhooks.vue
  • src/stores/webhooks.ts
  • src/components/WebhookForm.vue
  • src/components/WebhookDeliveryLog.vue
  • src/constants/organizationTabs.ts
src/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend ESLint must pass before commit; run bun lint:fix to auto-fix issues in frontend files

Files:

  • src/pages/settings/organization/Webhooks.vue
  • src/stores/webhooks.ts
  • src/components/WebhookForm.vue
  • src/components/WebhookDeliveryLog.vue
  • src/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.vue
  • src/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 with supabase 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 the process_all_cron_tasks function in a new migration file to add your job if needed

Database 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.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/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.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • 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: 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.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • 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: 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.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • 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: 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
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/triggers/index.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • 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: 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
  • supabase/functions/triggers/index.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/triggers/webhook_dispatcher.ts
  • supabase/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.ts
  • supabase/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.ts
  • supabase/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.vue
  • src/components/WebhookForm.vue
  • src/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 createHono from 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 getBodyOrQuery for 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_delivery queue is explicitly created in the webhooks migration (20251226133417_webhooks.sql). Parameter binding is correct: $1 accepts the queue name as text and $2::jsonb properly 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 computed isEditing property.

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 storeToRefs for 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 status for 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 retryDelivery function 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_TYPES is properly typed with as const for type safety. Good separation of concerns.


372-377: No action needed: frontend and backend behavior is aligned.

The local state update setting attempt_count = 0 is correct. The backend's retryDelivery function (in supabase/functions/_backend/public/webhooks/deliveries.ts) explicitly sets attempt_count: 0 in 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_dispatcher and webhook_delivery queues 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 for webhook_deliveries is appropriate since cleanup is handled by the scheduled cleanup_webhook_deliveries function.


215-263: Trigger function is correctly implemented.

The conditional trigger creation (checking if audit_logs exists) handles migration ordering gracefully. The payload includes all necessary audit log fields for webhook dispatch.

Comment on lines +3 to +15
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>()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment on lines +82 to +96
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,
},
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +302 to +309
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";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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";

afterAll(async () => {
// Clean up created webhooks
if (createdWebhookId) {
await getSupabaseClient().from('webhooks').delete().eq('id', createdWebhookId)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.ts

Or 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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
supabase/functions/_backend/utils/webhook.ts (1)

82-96: Use audit log timestamp in webhook payload, not new 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 own created_at so 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 URLs

The 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 .localhost subdomains 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: Apply status filter before pagination and mirror it in the count query

Right now:

  • The main query orders and paginates first, then conditionally applies .eq('status', body.status).
  • The total-count query ignores status entirely.

This means filtered pages can be partially empty and total/has_more don’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

queueWebhookDelivery and queueWebhookDeliveryWithDelay catch and log errors from pgmq.send but never rethrow or return a failure indicator. Callers like retryDelivery then 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/Result so 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

📥 Commits

Reviewing files that changed from the base of the PR and between cf6d0fd and f4a6178.

📒 Files selected for processing (11)
  • src/types/supabase.types.ts
  • supabase/functions/_backend/public/webhooks/delete.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/get.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/functions/_backend/utils/webhook.ts
  • tests/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.ts
  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/functions/_backend/public/webhooks/put.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • src/types/supabase.types.ts
  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/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_go for 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 from src/ 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:fix to 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.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/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.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/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 in supabase/functions/_backend/ as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/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:backend for backend files

Files:

  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/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.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/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.ts
  • 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} : 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.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/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.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/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.ts
  • supabase/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.ts
  • supabase/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.ts
  • supabase/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.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/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: New cache_entry table type looks consistent

The cache_entry Row/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: Backend cache_entry type mirrors frontend and is coherent

The new cache_entry table 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 utilities

Body 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

checkWebhookPermission centralizes org/admin checks, and the router cleanly delegates each route to its handler behind middlewareKey. 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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between f4a6178 and 5b6e35f.

📒 Files selected for processing (14)
  • messages/de.json
  • messages/es.json
  • messages/fr.json
  • messages/hi.json
  • messages/id.json
  • messages/it.json
  • messages/ja.json
  • messages/ko.json
  • messages/pl.json
  • messages/pt-br.json
  • messages/ru.json
  • messages/tr.json
  • messages/vi.json
  • messages/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-bundle line 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-bundle line 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 consistent

The 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-formed

The 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.

Comment on lines +1137 to +1159
"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."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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 keys add-webhook, create-first-webhook, create-webhook, delete-webhook, delete-webhook-confirm, edit-webhook, no-webhooks, and no-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.

Comment on lines +1138 to +1159
"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を設定します。"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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-webhook
  • create-first-webhook
  • create-webhook
  • delete-webhook
  • delete-webhook-confirm
  • edit-webhook
  • no-webhooks
  • no-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.

Comment on lines +1137 to +1159
"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-уведомлений при возникновении событий в вашей организации."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -5

Repository: 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)}")
EOF

Repository: 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-webhook
  • create-webhook
  • create-first-webhook
  • delete-webhook
  • delete-webhook-confirm
  • edit-webhook
  • no-webhooks
  • no-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.

Comment on lines +1137 to +1159
"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通知。"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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 as add-webhook, create-first-webhook, create-webhook, delete-webhook, delete-webhook-confirm, edit-webhook, no-webhooks, and no-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.

riderx and others added 5 commits December 27, 2025 01:21
- 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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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‑CN

The 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 by Webhooks.vue, so parts of the webhooks page will fall back to English:

  • add-webhook
  • create-first-webhook
  • create-webhook
  • delete-webhook
  • delete-webhook-confirm
  • edit-webhook
  • no-webhooks
  • no-webhooks-description
  • subscribed-events
  • test
  • view-deliveries

Consider 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 missing

The 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 that Webhooks.vue relies on for the main UI:

  • add-webhook
  • create-first-webhook
  • create-webhook
  • delete-webhook
  • delete-webhook-confirm
  • edit-webhook
  • no-webhooks
  • no-webhooks-description
  • subscribed-events
  • test
  • view-deliveries

Without 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.json semantics 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 Japanese

The newly added webhook-* and webhooks* strings look consistent, but the higher‑level UI keys used for buttons and empty states are still missing in this locale (they exist in messages/de.json and messages/es.json):

  • add-webhook
  • create-first-webhook
  • create-webhook
  • delete-webhook
  • delete-webhook-confirm
  • edit-webhook
  • no-webhooks
  • no-webhooks-description

Without 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 to anon role remain.

As previously noted, granting ALL privileges on webhook tables to anon is overly permissive. While RLS policies protect the data, webhooks are organization-specific authenticated features and anon should 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 duplicate fetchWebhooks calls on initial load

onMounted calls webhooksStore.fetchWebhooks(), and the watch(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 in onMounted, or adding a simple guard in the watcher to skip when the organization hasn’t actually changed.


126-142: Make testingWebhookId clearing more defensive

testWebhook sets and clears testingWebhookId around the async call, which is good for the loading state. If webhooksStore.testWebhook were ever to throw (network error not wrapped into { success: false }), testingWebhookId would 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 duplication

All 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-btn variants or a small Button component) 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 improvements

This 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 / deliveries state looks consistent and side‑effect free.

Two optional refinements you might consider:

  • Extract a tiny helper like getOrgIdOrError() to avoid repeating the useOrganizationStore().currentOrganization?.gid + “No organization selected” handling across each action.
  • Narrow WebhookDelivery.request_payload from any once 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b6e35f and 32e9116.

📒 Files selected for processing (23)
  • messages/de.json
  • messages/en.json
  • messages/es.json
  • messages/fr.json
  • messages/hi.json
  • messages/id.json
  • messages/it.json
  • messages/ja.json
  • messages/ko.json
  • messages/pl.json
  • messages/pt-br.json
  • messages/ru.json
  • messages/tr.json
  • messages/vi.json
  • messages/zh-cn.json
  • src/components/WebhookForm.vue
  • src/constants/organizationTabs.ts
  • src/pages/settings/organization/Webhooks.vue
  • src/stores/webhooks.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/post.ts
  • supabase/functions/_backend/public/webhooks/put.ts
  • supabase/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.ts
  • src/pages/settings/organization/Webhooks.vue
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

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_go for native mobile functionality

Files:

  • src/stores/webhooks.ts
  • src/pages/settings/organization/Webhooks.vue
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ directory in frontend TypeScript and Vue components

Files:

  • src/stores/webhooks.ts
  • src/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:fix to auto-fix issues in frontend files

Files:

  • src/stores/webhooks.ts
  • 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 konsta anywhere else in the app

Files:

  • src/stores/webhooks.ts
  • src/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
Use useRoute() from vue-router to access route parameters and useRouter() for programmatic navigation in Vue components

Use 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 with unplugin-vue-router and types are available in src/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, 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
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 the process_all_cron_tasks function 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 consistent

The added webhook‑related keys (add-webhook, create-webhook, delete/edit/empty‑state strings, plus all webhook-* 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 surface

All expected webhook keys are present (add-webhook, create/delete/edit, empty‑state texts, and the full webhook-* 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_id correctly supports test events.


61-204: LGTM: RLS policies properly enforce organization-level access control.

The policies correctly use check_min_rights to 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 DEFINER to 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_logs table 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 DEFINER is 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_dispatcher and webhook_delivery queues 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.

Comment on lines +1178 to +1200
"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 सूचनाएं प्राप्त करने के लिए वेबहुक कॉन्फ़िगर करें।"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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-webhook
  • delete-webhook, delete-webhook-confirm, edit-webhook
  • no-webhooks, no-webhooks-description
  • subscribed-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.

Comment on lines +1178 to +1200
"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 알림을 받도록 웹훅을 구성합니다."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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-webhook
  • delete-webhook, delete-webhook-confirm, edit-webhook
  • no-webhooks, no-webhooks-description
  • subscribed-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.

Comment on lines +1178 to +1200
"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."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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-webhook
  • create-first-webhook
  • create-webhook
  • delete-webhook
  • delete-webhook-confirm
  • edit-webhook
  • no-webhooks
  • no-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).

Comment on lines +144 to +158
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'))
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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 toggleWebhook returns 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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
supabase/functions/_backend/public/webhooks/deliveries.ts (1)

13-18: Consider validating the status field 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 describe block depend on createdWebhookId being set by the earlier [POST] /webhooks test. 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/test if it fails (as noted in the comment at line 411).

Example approach:

  1. Get deliveries after the test webhook call
  2. If any delivery has status 'failed' or 'pending', use it for retry test
  3. Verify the retry returns success and the delivery is re-queued
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32e9116 and 19982fb.

📒 Files selected for processing (3)
  • supabase/config.toml
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • tests/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.ts
  • supabase/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 ~/ to src/

Files:

  • tests/webhooks.test.ts
  • supabase/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.ts including getEndpointUrl(path) for correct worker routing and USE_CLOUDFLARE_WORKERS=true for 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 in supabase/functions/_backend/ as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() 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:backend for 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 correct import_map path.

supabase/functions/_backend/public/webhooks/deliveries.ts (2)

1-11: LGTM!

Imports are well-organized and use the correct paths. Using zod/mini aligns with the library context for schema validation.


27-96: LGTM!

The getDeliveries function 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.

Comment on lines +132 to +144
// 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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
// 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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.enabled to infer the new state, but toggleWebhook calls updateWebhook which mutates webhooks.value[index]. Since webhook is 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.

onMounted only runs once. If webhook prop 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 with v-if="showForm"), but consider using a watch with immediate: true for 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 casting supabase as any bypasses type safety.

The (supabase as any) cast hides potential type mismatches between the Supabase client and the webhooks table 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 pending before 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

📥 Commits

Reviewing files that changed from the base of the PR and between 19982fb and 3ba679e.

📒 Files selected for processing (3)
  • src/components/WebhookForm.vue
  • src/pages/settings/organization/Webhooks.vue
  • src/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.ts
  • src/pages/settings/organization/Webhooks.vue
  • src/components/WebhookForm.vue
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

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_go for native mobile functionality

Files:

  • src/stores/webhooks.ts
  • src/pages/settings/organization/Webhooks.vue
  • src/components/WebhookForm.vue
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ directory in frontend TypeScript and Vue components

Files:

  • src/stores/webhooks.ts
  • src/pages/settings/organization/Webhooks.vue
  • src/components/WebhookForm.vue
src/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend ESLint must pass before commit; run bun lint:fix to auto-fix issues in frontend files

Files:

  • src/stores/webhooks.ts
  • src/pages/settings/organization/Webhooks.vue
  • src/components/WebhookForm.vue
src/**/*.{vue,ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Konsta components are reserved for the safe area helpers; avoid importing konsta anywhere else in the app

Files:

  • src/stores/webhooks.ts
  • src/pages/settings/organization/Webhooks.vue
  • src/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
Use useRoute() from vue-router to access route parameters and useRouter() for programmatic navigation in Vue components

Use 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/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 with unplugin-vue-router and types are available in src/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/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.vue
  • src/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.vue
  • src/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.vue
  • src/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 storeToRefs for reactive state, and proper i18n setup.


72-96: LGTM! Clean form submission handling with proper create/edit separation.

The destructuring to omit enabled on 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 settings layout 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). The owner role is deliberately excluded from organization permission management; it is handled separately as the entity creator (via created_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_TYPES constant with as const for 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 reset function properly clears all state for cleanup during organization switches or logout.


64-64: No changes required. The useSupabase() 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 cached SupabaseClient instance 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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 than auditLogData.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 of any.

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 createWebhook and updateWebhook. 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' and attempt_count: 0 in 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3ba679e and 8b86dc0.

📒 Files selected for processing (2)
  • src/stores/webhooks.ts
  • supabase/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.ts
  • supabase/functions/_backend/utils/webhook.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • src/stores/webhooks.ts
  • supabase/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_go for native mobile functionality

Files:

  • src/stores/webhooks.ts
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ 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:fix to 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 konsta anywhere 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 in supabase/functions/_backend/ as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() 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:backend for 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-level useSupabase() 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_send RPC by using direct SQL via getPgClient. The try/finally pattern ensures proper client cleanup. As per coding guidelines, this correctly uses getPgClient() from utils/pg.ts for 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.

Comment on lines +400 to +406
deliveries.value = data || []
deliveryPagination.value = {
page,
per_page: DELIVERIES_PER_PAGE,
total: count ?? 0,
has_more: (data?.length ?? 0) === DELIVERIES_PER_PAGE,
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +160 to +186
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,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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 -100

Repository: 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 -20

Repository: 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.ts

Repository: 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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 using auditLogData.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: Use createHono instead of direct new Hono() instantiation.

Per coding guidelines, use createHono from utils/hono.ts for 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.enabled after the async toggle, assuming webhook.enabled still holds the pre-toggle value. If the store mutates the webhook object during toggleWebhook, 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 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 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8b86dc0 and db48a5f.

📒 Files selected for processing (7)
  • messages/en.json
  • src/pages/settings/organization/Webhooks.vue
  • src/stores/webhooks.ts
  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/functions/_backend/utils/webhook.ts
  • supabase/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, 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
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 the process_all_cron_tasks function 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.ts
  • src/pages/settings/organization/Webhooks.vue
  • src/stores/webhooks.ts
  • supabase/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.ts
  • supabase/functions/_backend/utils/webhook.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • src/stores/webhooks.ts
  • supabase/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.ts
  • 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 in supabase/functions/_backend/ as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • 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:backend for backend files

Files:

  • supabase/functions/_backend/triggers/webhook_delivery.ts
  • supabase/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
Use useRoute() from vue-router to access route parameters and useRouter() for programmatic navigation in Vue components

Use 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 with unplugin-vue-router and types are available in src/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_go for native mobile functionality

Files:

  • src/pages/settings/organization/Webhooks.vue
  • src/stores/webhooks.ts
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ directory in frontend TypeScript and Vue components

Files:

  • src/pages/settings/organization/Webhooks.vue
  • src/stores/webhooks.ts
src/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend ESLint must pass before commit; run bun lint:fix to auto-fix issues in frontend files

Files:

  • src/pages/settings/organization/Webhooks.vue
  • src/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 konsta anywhere else in the app

Files:

  • src/pages/settings/organization/Webhooks.vue
  • src/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.sql
  • 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/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.sql
  • supabase/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 EXISTS ensures 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.send directly via the pg client, avoiding the RPC issue noted in previous reviews. The fallback mechanism in queueWebhookDeliveryWithDelay provides good resilience, and closeClient is properly called in the finally block.

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 since webhook was 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 using hasPermissionsInRole.

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",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
"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.

Comment on lines +438 to +444
// 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
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
// 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).

Comment on lines +301 to +324
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
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

riderx and others added 2 commits December 28, 2025 00:24
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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
supabase/migrations/20251226133417_webhooks.sql (1)

309-316: Overly permissive grants to anon role remain unaddressed.

As flagged in a previous review, granting ALL to the anon role on webhook tables is unusual and risky. While RLS policies don't grant anon any access (policies only target authenticated and service_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 webhooks table design is well-thought-out with appropriate foreign keys, cascade behaviors, and indexes. The partial index on (org_id, enabled) WHERE enabled = true is efficient for querying active webhooks.

Consider adding a CHECK constraint on the events array 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_id column enables efficient RLS without joins. The partial index on pending retries is excellent for the retry worker.

Minor suggestion: add a CHECK constraint on status to 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

📥 Commits

Reviewing files that changed from the base of the PR and between db48a5f and aa4bad1.

📒 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, 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
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 the process_all_cron_tasks function 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 for webhook_deliveries is correct since cleanup is handled by the cron job.


219-248: Trigger function is correctly secured.

The function properly uses SECURITY DEFINER with SET search_path = '' to prevent search path injection attacks. The pgmq.send call is schema-qualified, and the payload structure includes all necessary audit log fields for downstream processing.


273-287: LGTM!

Simple and correct updated_at auto-update pattern with proper search_path security.


340-347: Webhook queues properly integrated into cron processing.

The webhook_dispatcher and webhook_delivery queues are correctly added to the high-frequency processing (every 10 seconds), and cleanup_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: the audit_logs table migration (20251226125240) precedes this webhooks migration (20251226133417), so the audit_logs table will exist when the trigger is created. The conditional check is safe defensive programming.

Comment on lines +213 to +214
SELECT pgmq.create('webhook_dispatcher');
SELECT pgmq.create('webhook_delivery');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 -20

Repository: 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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 for attempt_count. This results in two round trips to the database. Consider refactoring updateDeliveryResult to accept an optional attemptCount parameter so both updates can be done in a single query.

💡 Conceptual refactor to reduce database round trips

Modify the updateDeliveryResult utility 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_count in 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 cloudlog from ../../utils/log.ts or wherever the logging utility is exported.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa4bad1 and 9c09302.

📒 Files selected for processing (3)
  • supabase/functions/_backend/public/webhooks/deliveries.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/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.ts
  • supabase/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.ts
  • supabase/functions/_backend/public/webhooks/deliveries.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • supabase/functions/_backend/public/webhooks/test.ts
  • 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/test.ts
  • 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 in supabase/functions/_backend/ as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/public/webhooks/test.ts
  • 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:backend for backend files

Files:

  • supabase/functions/_backend/public/webhooks/test.ts
  • supabase/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.ts
  • 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/**/*.{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 safeParse and performs permission checks using checkWebhookPermissionV2, 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 checkWebhookPermissionV2 already validated org access. The type assertion with explanatory comment is acceptable for the current state.

Comment on lines +30 to +34
const { data: webhook, error: fetchError } = await (supabaseAdmin(c) as any)
.from('webhooks')
.select('*')
.eq('id', body.webhookId)
.single()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Get the full test.ts file and surrounding context
head -100 supabase/functions/_backend/public/webhooks/test.ts

Repository: 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 js

Repository: 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.ts

Repository: 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.

riderx and others added 3 commits December 28, 2025 02:22
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>
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant