Skip to content

Webhooks + API for automating conversation flows and knowledge base management#27

Open
GeorgiZhelev wants to merge 13 commits intoopencom-org:devfrom
GeorgiZhelev:feat/automation-api-webhook-scaffolding
Open

Webhooks + API for automating conversation flows and knowledge base management#27
GeorgiZhelev wants to merge 13 commits intoopencom-org:devfrom
GeorgiZhelev:feat/automation-api-webhook-scaffolding

Conversation

@GeorgiZhelev
Copy link

  • feat(convex): add automation api and webhook scaffolding
  • feat(convex): refine automation api and webhook scaffolding
  • Make API and webhooks more robust
  • add automation API CRUD for articles & collections (2.1b)

@vercel
Copy link

vercel bot commented Mar 13, 2026

@GeorgiZhelev is attempting to deploy a commit to the djanogly's projects Team on Vercel.

A member of the Team first needs to authorize it.

@GeorgiZhelev GeorgiZhelev marked this pull request as ready for review March 17, 2026 11:43
@qodo-code-review
Copy link

Review Summary by Qodo

Automation API and webhook system with full CRUD operations, event delivery, and credential management

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• **Comprehensive automation API and webhook system** with full CRUD operations for conversations,
  messages, visitors, tickets, articles, collections, and outbound messages
• **Webhook delivery system** with HMAC-SHA256 signing, exponential backoff retry logic (5
  attempts), and manual replay functionality
• **API credential management** with secure osk_ prefixed credentials, SHA-256 hashing,
  scope-based access control, and audit logging
• **Authentication and rate limiting** via withAutomationAuth middleware with workspace-level (120
  req/min) and credential-level (60 req/min) limits
• **Conversation claim management** for external automation with 5-minute lease expiration, renewal
  support, and escalation/release operations
• **Event emission system** that triggers webhook deliveries for matching subscriptions with
  cursor-based pagination and filtering
• **AI agent automation claim suppression** to prevent responses when conversation is claimed by
  external automation
• **Article and collection write operations** refactored into reusable core helpers with embedding
  management and status transitions
• **Database schema** for automation credentials, events, webhooks, deliveries, conversation claims,
  and idempotency keys
• **HTTP API routes** exposing 40+ endpoints under /api/v1/ namespace with proper error handling
  and validation
• **Webhook secret encryption** using AES-GCM with environment-based key management
• **Comprehensive test coverage** including CRUD operations, security, idempotency, rate limiting,
  pagination, and event emission
• **React hooks and UI components** for managing credentials, webhooks, and delivery logs in
  settings
• **Cron jobs** for expiring stale claims and cleaning up expired idempotency keys
• **Audit logging** with 24 new automation-related action types
Diagram
flowchart LR
  A["API Credentials<br/>osk_ prefix<br/>SHA-256 hash"] -->|withAutomationAuth| B["Rate Limiting<br/>120/min workspace<br/>60/min credential"]
  B -->|validates| C["HTTP Routes<br/>40+ endpoints<br/>/api/v1/"]
  C -->|CRUD ops| D["Resources<br/>Conversations<br/>Messages<br/>Tickets<br/>Articles"]
  D -->|emits| E["Automation Events<br/>Cursor pagination<br/>Filtering"]
  E -->|triggers| F["Webhook Subscriptions<br/>Event filtering<br/>Resource types"]
  F -->|delivers| G["Webhook Worker<br/>HMAC-SHA256<br/>Exponential backoff<br/>5 retries"]
  H["Conversation Claims<br/>5-min lease<br/>Renewal support"] -->|suppresses| I["AI Agent<br/>Claim detection<br/>Graceful handling"]
  J["Article/Collection<br/>Core Helpers<br/>Embedding mgmt"] -->|refactored| D
Loading

Grey Divider

File Changes

1. packages/convex/tests/automationFixes.test.ts 🧪 Tests +2817/-0

Comprehensive automation API and webhook system test coverage

• Comprehensive test suite for automation API fixes covering cross-workspace security, idempotency,
 rate limiting, and webhook delivery
• Tests for webhook subscription filtering by resource type, channel, and AI workflow state
• Tests for conversation claim operations (release/escalate) and delivery attempt history with retry
 logic
• Tests for webhook secret encryption, event pagination with same-timestamp tie-breaking, and sync
 cursor correctness
• Tests for articles and collections CRUD operations with validation, audit logging, and status
 transitions
• Tests for event emission from automation API write paths with proper event data and PII filtering

packages/convex/tests/automationFixes.test.ts


2. packages/convex/tests/automationOutboundMessages.test.ts 🧪 Tests +659/-0

Outbound messages automation API test suite

• Complete test suite for outbound message CRUD lifecycle (create, get, update, list, delete)
• Tests for workspace isolation preventing cross-workspace access and modifications
• Tests for activate/pause status transitions and chat-type enforcement
• Tests for pagination with same-timestamp handling and updatedSince filtering
• Tests for status filtering and targeting rule validation on create/update operations

packages/convex/tests/automationOutboundMessages.test.ts


3. packages/convex/convex/articles.ts ✨ Enhancement +55/-73

Extract article write operations into reusable core helpers

• Refactored article creation to use new createArticleCore helper function
• Refactored article deletion to use deleteArticleCore helper with embedding cleanup
• Refactored publish/unpublish/archive operations to use dedicated core helpers for non-legacy
 articles
• Preserved legacy internal article handling with separate code paths

packages/convex/convex/articles.ts


View more (55)
4. packages/convex/convex/schema/helpCenterTables.ts ⚙️ Configuration changes +4/-2

Add workspace-scoped updatedAt indexes for pagination

• Added by_workspace_updated_at index to collections table for efficient sorting by update
 timestamp
• Added by_workspace_updated_at index to articles table for efficient sorting by update
 timestamp

packages/convex/convex/schema/helpCenterTables.ts


5. packages/convex/convex/automationApiInternals.ts ✨ Enhancement +1812/-0

Core automation API internals with full CRUD operations

• Implements comprehensive internal API for automation CRUD operations across conversations,
 messages, visitors, tickets, articles, collections, and outbound messages
• Provides pagination support with cursor-based navigation and filtering capabilities for all
 resource types
• Includes idempotent message sending with 24-hour TTL for deduplication
• Integrates audit logging and automation event emission for all operations

packages/convex/convex/automationApiInternals.ts


6. packages/convex/convex/automationHttpRoutes.ts ✨ Enhancement +901/-0

HTTP API routes for automation endpoints

• Exposes HTTP endpoints for all automation API operations with authentication via
 withAutomationAuth
• Implements error handling with proper HTTP status codes and validation error messages
• Provides RESTful routes for conversations, messages, visitors, tickets, articles, collections, and
 outbound messages
• Includes webhook replay functionality and event feed endpoint

packages/convex/convex/automationHttpRoutes.ts


7. packages/convex/convex/aiAgentActions.ts ✨ Enhancement +124/-39

AI agent automation claim suppression logic

• Adds automation claim detection to prevent AI agent from responding when conversation is claimed
 by external automation
• Implements getAutomationClaimSuppression() to check for active claims and suppress AI responses
• Wraps handoff and bot message operations with claim-safe handlers to gracefully handle claim
 conflicts
• Returns early with suppression result if active automation claim is detected

packages/convex/convex/aiAgentActions.ts


8. packages/convex/convex/automationWebhookWorker.ts ✨ Enhancement +306/-0

Webhook delivery worker with retry and replay

• Implements webhook delivery system with HMAC-SHA256 signing for security
• Provides exponential backoff retry logic with 5 attempts and configurable delays (30s to 4h)
• Includes webhook replay functionality for manual retries of failed deliveries
• Handles delivery status tracking and error persistence

packages/convex/convex/automationWebhookWorker.ts


9. packages/convex/convex/automationConversationClaims.ts ✨ Enhancement +212/-0

Conversation claim management for automation

• Manages conversation claims for external automation with 5-minute lease expiration
• Supports claim renewal for same credential and prevents concurrent claims from different
 automations
• Provides escalation and release operations to transition conversations back to human queue
• Includes stale claim expiration cleanup and active claim lookup for AI agent integration

packages/convex/convex/automationConversationClaims.ts


10. packages/convex/convex/automationWebhooks.ts ✨ Enhancement +247/-0

Webhook subscription management and delivery tracking

• Implements webhook subscription management with CRUD operations (createSubscription,
 listSubscriptions, updateSubscription, deleteSubscription)
• Adds webhook testing and delivery replay functionality with event filtering by type, resource,
 channel, and workflow state
• Generates and manages webhook signing secrets with secure prefix-based identification
• Implements delivery listing with pagination and status filtering

packages/convex/convex/automationWebhooks.ts


11. packages/convex/convex/lib/articleWriteHelpers.ts ✨ Enhancement +252/-0

Article write operations refactored into core helpers

• Extracts article CRUD operations into reusable helper functions (createArticleCore,
 updateArticleCore, deleteArticleCore, publishArticleCore, unpublishArticleCore,
 archiveArticleCore)
• Handles embedding generation and removal based on article status and visibility changes
• Manages slug generation and uniqueness validation for articles
• Implements status transition logic with proper publishedAt timestamp handling

packages/convex/convex/lib/articleWriteHelpers.ts


12. packages/convex/convex/automationCredentials.ts ✨ Enhancement +236/-0

Automation API credential management system

• Implements API credential management with CRUD operations (create, list, get, rotate,
 disable, enable, remove)
• Generates secure credentials with osk_ prefix and SHA-256 hashing for secret storage
• Enforces scope-based access control and logs all credential actions to audit logs
• Supports credential expiration and status management

packages/convex/convex/automationCredentials.ts


13. packages/convex/convex/lib/automationAuth.ts ✨ Enhancement +207/-0

Automation API authentication and rate limiting

• Implements authentication middleware for automation API requests via withAutomationAuth function
• Validates API credentials by secret hash and checks scope requirements
• Enforces workspace-level (120 req/min) and credential-level (60 req/min) rate limiting
• Provides internal query and mutation functions for credential lookup and rate limit checking

packages/convex/convex/lib/automationAuth.ts


14. packages/convex/convex/automationEvents.ts ✨ Enhancement +171/-0

Automation event emission and webhook delivery scheduling

• Implements event emission system that triggers webhook deliveries for matching subscriptions
• Filters events based on subscription criteria (event types, resource types, channels, workflow
 states)
• Provides poll-based event feed with cursor-based pagination for automation clients
• Creates pending webhook deliveries and schedules them for processing

packages/convex/convex/automationEvents.ts


15. apps/web/src/app/settings/hooks/useAutomationApiConvex.ts ✨ Enhancement +157/-0

React hook for automation API management

• Provides React hook for managing automation API credentials and webhooks
• Exposes query and mutation functions for credential CRUD, subscription management, and delivery
 replay
• Defines TypeScript types for credentials, subscriptions, and deliveries
• Integrates with Convex backend through web mutation and query references

apps/web/src/app/settings/hooks/useAutomationApiConvex.ts


16. packages/convex/convex/collections.ts ✨ Enhancement +13/-104

Collection operations refactored to use helpers

• Refactors collection creation, update, and deletion logic to use new collectionWriteHelpers
 module
• Simplifies mutation handlers by delegating core operations to extracted helper functions
• Maintains existing permission and validation logic while improving code reusability

packages/convex/convex/collections.ts


17. packages/convex/convex/lib/collectionWriteHelpers.ts ✨ Enhancement +157/-0

Collection write operations refactored into core helpers

• Extracts collection CRUD operations into reusable helper functions (createCollectionCore,
 updateCollectionCore, deleteCollectionCore)
• Implements slug generation, uniqueness validation, and parent collection validation
• Includes cycle detection to prevent collections from becoming their own descendants
• Validates child collection and article constraints before deletion

packages/convex/convex/lib/collectionWriteHelpers.ts


18. packages/convex/convex/http.ts ✨ Enhancement +76/-0

Automation API v1 HTTP routes registration

• Adds 40+ HTTP routes for automation API v1 endpoints covering conversations, messages, visitors,
 tickets, articles, collections, and outbound messages
• Imports and registers handlers for CRUD operations, event feeds, and webhook replay functionality
• Establishes /api/v1/ namespace for all automation API endpoints

packages/convex/convex/http.ts


19. packages/convex/convex/schema/automationTables.ts ✨ Enhancement +111/-0

Database schema for automation API and webhooks

• Defines database schema for automation credentials with secret hashing and rate limit tracking
• Defines schema for automation events with workspace and timestamp indexing
• Defines webhook subscriptions with encryption support for signing secrets and event filtering
 options
• Defines webhook deliveries, conversation claims, idempotency keys, and workspace rate limits
 tables

packages/convex/convex/schema/automationTables.ts


20. packages/convex/tests/automationApiHelpers.test.ts 🧪 Tests +107/-0

Unit tests for API helper functions

• Tests pagination parameter parsing with defaults, clamping, and validation
• Tests paginated response building with cursor extraction and truncation
• Tests JSON and error response helpers

packages/convex/tests/automationApiHelpers.test.ts


21. packages/convex/convex/conversations.ts ✨ Enhancement +36/-1

Conversation event emission for automation

• Adds automation event emission on conversation creation and status updates
• Emits events with conversation metadata including channel, status, and visitor information
• Integrates emitAutomationEvent calls into existing conversation mutation handlers

packages/convex/convex/conversations.ts


22. apps/web/src/app/settings/settingsSections.ts ⚙️ Configuration changes +19/-8

Settings section configuration for automation API

• Adds new automation-api settings section with keywords for discoverability
• Updates priority and mobile order for subsequent sections to accommodate new section
• Configures section as admin-only with default collapsed state

apps/web/src/app/settings/settingsSections.ts


23. packages/convex/convex/messages.ts ✨ Enhancement +30/-0

Message event emission and automation claim validation

• Adds automation event emission on message creation for both user and bot messages
• Implements claim validation to prevent AI agent from sending messages during active automation
 claims
• Emits events with message metadata including conversation ID, sender type, and channel

packages/convex/convex/messages.ts


24. packages/convex/tests/aiAgentRuntimeSafety.test.ts 🧪 Tests +85/-0

AI agent automation claim suppression test

• Adds test case verifying AI agent suppresses response when automation claim becomes active before
 message persistence
• Tests that handoff occurs with appropriate reason and no message is created
• Validates that no mutations are triggered when claim is detected

packages/convex/tests/aiAgentRuntimeSafety.test.ts


25. packages/convex/convex/lib/automationWebhookSecrets.ts ✨ Enhancement +83/-0

Webhook secret encryption and decryption utilities

• Implements AES-GCM encryption and decryption for webhook signing secrets
• Uses environment variable AUTOMATION_WEBHOOK_SECRET_ENCRYPTION_KEY for key management
• Encodes encrypted secrets as base64(iv) + "." + base64(ciphertext+tag) format
• Provides utility functions for base64 conversion and crypto key import

packages/convex/convex/lib/automationWebhookSecrets.ts


26. packages/convex/tests/automationScopes.test.ts 🧪 Tests +67/-0

Unit tests for automation scope validation

• Tests scope validation for all 18 defined automation scopes
• Tests rejection of invalid scopes with comprehensive error messages
• Validates scope array processing and empty array handling

packages/convex/tests/automationScopes.test.ts


27. packages/convex/convex/tickets.ts ✨ Enhancement +48/-0

Ticket event emission for automation

• Adds automation event emission on ticket creation, updates, and comment additions
• Emits events with ticket metadata including channel, status, priority, and assignee information
• Integrates event emission into ticket creation, update, conversion, comment, and resolution flows

packages/convex/convex/tickets.ts


28. packages/convex/convex/lib/apiHelpers.ts ✨ Enhancement +67/-0

API helper utilities for HTTP endpoints

• Provides pagination parameter parsing with limit clamping (1-100) and cursor support
• Implements paginated response building with optional cursor extraction
• Provides JSON and error response helpers for HTTP endpoints
• Includes cursor encoding/decoding utilities and Convex ID validation

packages/convex/convex/lib/apiHelpers.ts


29. packages/convex/convex/testing/helpers/automation.ts 🧪 Tests +69/-0

Test helpers for automation API

• Provides test helper mutations for enabling automation API and creating test credentials
• Generates test credentials with all scopes and configurable metadata
• Supports credential creation with custom scopes and actor names for testing

packages/convex/convex/testing/helpers/automation.ts


30. packages/convex/tests/automationCredentials.test.ts 🧪 Tests +58/-0

Authentication tests for automation API

• Tests that credential and webhook management endpoints require authentication
• Validates that unauthenticated requests are properly rejected

packages/convex/tests/automationCredentials.test.ts


31. packages/convex/convex/automationScopes.ts ✨ Enhancement +57/-0

Automation API scope definitions and validation

• Defines 18 automation scopes covering conversations, messages, visitors, tickets, articles,
 collections, outbound messages, webhooks, and claims
• Provides scope validation functions and Convex value validator for scope enforcement
• Exports AutomationScope type for type-safe scope handling

packages/convex/convex/automationScopes.ts


32. packages/convex/convex/migrations/backfillAutomationWebhookSecrets.ts ✨ Enhancement +58/-0

Migration for webhook secret encryption

• Implements migration to encrypt legacy plaintext webhook secrets
• Provides batch processing with configurable batch size for gradual migration
• Includes verification mutation to check migration completion status

packages/convex/convex/migrations/backfillAutomationWebhookSecrets.ts


33. apps/web/src/app/settings/hooks/useSettingsPageController.ts ✨ Enhancement +5/-1

Settings page controller automation API visibility

• Adds visibility check for automation-api section based on admin status and workspace feature
 flag
• Updates dependency array to include automationApiEnabled flag
• Adds status indicator for automation API section

apps/web/src/app/settings/hooks/useSettingsPageController.ts


34. packages/convex/convex/schema/outboundSupportTables.ts ✨ Enhancement +3/-1

Database indexes for outbound and ticket queries

• Adds index by_workspace_type_updated_at to outboundMessages table for efficient filtering
• Adds index by_workspace_updated_at to tickets table for timestamp-based queries

packages/convex/convex/schema/outboundSupportTables.ts


35. packages/convex/convex/testing/helpers.ts ✨ Enhancement +3/-0

Test helpers module integration

• Exports automation test helpers (enableAutomationApi, createTestAutomationCredential) for test
 suite
• Integrates automation helpers into main testing helpers module

packages/convex/convex/testing/helpers.ts


36. packages/convex/convex/auditLogs.ts ✨ Enhancement +22/-1

Audit log action types for automation

• Adds 24 new automation-related audit action types covering message, conversation, visitor, ticket,
 article, collection, and outbound message operations
• Extends AuditAction type to include automation workflow events

packages/convex/convex/auditLogs.ts


37. packages/convex/convex/schema/inboxConversationTables.ts ✨ Enhancement +2/-0

Conversation and message schema updates

• Adds index by_workspace_updated_at to conversations table for efficient timestamp-based
 queries
• Adds optional automationCredentialId field to messages table for tracking automation-sent
 messages

packages/convex/convex/schema/inboxConversationTables.ts


38. packages/convex/convex/visitors/mutations.ts ✨ Enhancement +17/-0

Visitor event emission for automation

• Adds automation event emission on visitor identification and updates
• Emits events when visitors are merged or attributes are updated
• Integrates event emission into existing visitor mutation flows

packages/convex/convex/visitors/mutations.ts


39. packages/convex/convex/schema.ts ✨ Enhancement +2/-0

Schema integration for automation tables

• Imports and registers automationTables in the main schema definition
• Integrates automation database tables into the application schema

packages/convex/convex/schema.ts


40. packages/convex/convex/aiAgent.ts ✨ Enhancement +11/-0

AI agent automation claim validation

• Adds automation claim validation in handoffToHuman to prevent handoff when conversation is
 claimed
• Checks for active claims with future expiration before allowing human handoff

packages/convex/convex/aiAgent.ts


41. packages/convex/convex/crons.ts ✨ Enhancement +28/-0

Cron jobs for automation maintenance

• Implements cron jobs for expiring stale automation conversation claims every 5 minutes
• Implements cron job for cleaning up expired idempotency keys every hour

packages/convex/convex/crons.ts


42. packages/convex/convex/lib/idempotency.ts ✨ Enhancement +23/-0

Idempotency key cleanup utility

• Implements cleanup mutation for expired idempotency keys with batch processing
• Supports configurable batch size for gradual cleanup

packages/convex/convex/lib/idempotency.ts


43. packages/convex/convex/schema/authWorkspaceTables.ts ✨ Enhancement +2/-0

Workspace automation API feature flag

• Adds optional automationApiEnabled boolean field to workspace table for feature flag

packages/convex/convex/schema/authWorkspaceTables.ts


44. apps/web/src/app/settings/hooks/useSettingsPageConvex.ts ✨ Enhancement +1/-0

Workspace settings type update

• Adds optional automationApiEnabled field to WorkspaceSettingsRecord type

apps/web/src/app/settings/hooks/useSettingsPageConvex.ts


45. apps/web/src/app/settings/automation-api/WebhooksPanel.tsx ✨ Enhancement +394/-0

Webhook management UI component

• Implements UI component for webhook subscription management with create, edit, pause/resume, test,
 and delete operations
• Provides event type and resource type filtering with multi-checkbox selection
• Displays webhook status, signing secret prefix, and creation timestamp
• Includes secret display component with copy-to-clipboard functionality

apps/web/src/app/settings/automation-api/WebhooksPanel.tsx


46. apps/web/src/app/settings/page.tsx ✨ Enhancement +15/-0

Settings page automation API section integration

• Imports and renders AutomationApiSection component in settings page
• Conditionally displays automation API section based on admin status and feature flag
• Integrates automation API section into settings layout

apps/web/src/app/settings/page.tsx


47. openspec/changes/expose-automation-api-and-event-webhooks/specs/automation-access-governance/spec.md 📦 Other +1/-1
• Updates security requirement documentation to clarify webhook secret encryption and API credential
 hashing
• Specifies that API secrets use SHA-256 hashing and webhook secrets use server-side encryption

openspec/changes/expose-automation-api-and-event-webhooks/specs/automation-access-governance/spec.md


48. apps/web/src/app/settings/AutomationApiSection.tsx Additional files +65/-0

...

apps/web/src/app/settings/AutomationApiSection.tsx


49. apps/web/src/app/settings/automation-api/CredentialsPanel.tsx Additional files +246/-0

...

apps/web/src/app/settings/automation-api/CredentialsPanel.tsx


50. apps/web/src/app/settings/automation-api/DeliveryLogPanel.tsx Additional files +160/-0

...

apps/web/src/app/settings/automation-api/DeliveryLogPanel.tsx


51. apps/web/src/app/settings/automation-api/ScopeSelector.tsx Additional files +133/-0

...

apps/web/src/app/settings/automation-api/ScopeSelector.tsx


52. docs/api-reference.md Additional files +230/-12

...

docs/api-reference.md


53. docs/security.md Additional files +123/-1

...

docs/security.md


54. openspec/changes/expose-automation-api-and-event-webhooks/proposal.md Additional files +1/-1

...

openspec/changes/expose-automation-api-and-event-webhooks/proposal.md


55. openspec/changes/expose-automation-api-and-event-webhooks/specs/automation-resource-api/spec.md Additional files +10/-7

...

openspec/changes/expose-automation-api-and-event-webhooks/specs/automation-resource-api/spec.md


56. openspec/changes/expose-automation-api-and-event-webhooks/tasks.md Additional files +27/-19

...

openspec/changes/expose-automation-api-and-event-webhooks/tasks.md


57. openspec/changes/expose-automation-api-and-event-webhooks/v1-coverage-matrix.md Additional files +85/-0

...

openspec/changes/expose-automation-api-and-event-webhooks/v1-coverage-matrix.md


58. packages/convex/AUTOMATION_V1_COVERAGE.md Additional files +85/-0

...

packages/convex/AUTOMATION_V1_COVERAGE.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 17, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. fn builds untyped refs 📘 Rule violation ✓ Correctness
Description
The new fn(name: string) helper creates Convex refs from arbitrary strings via
makeFunctionReference(name) as any, weakening type safety and hardening guarantees. This also
applies makeFunctionReference("module:function") broadly rather than preferring generated
api/internal refs.
Code

packages/convex/convex/automationHttpRoutes.ts[R35-37]

+const fn = (name: string) => makeFunctionReference(name) as any;
+
+const listConversationsRef = fn("automationApiInternals:listConversationsForAutomation");
Evidence
PR Compliance IDs 4/5/6 forbid introducing string-based ref factories, require unsafe casts to be
tightly localized, and require preferring generated api/internal refs with
makeFunctionReference only as a localized workaround. The added fn factory uses
makeFunctionReference(name) as any and is then used to construct many cross-module refs from
strings.

AGENTS.md
AGENTS.md
AGENTS.md
packages/convex/convex/automationHttpRoutes.ts[32-78]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`packages/convex/convex/automationHttpRoutes.ts` introduces a string-based ref factory (`const fn = (name: string) =&gt; makeFunctionReference(name) as any;`) and uses it broadly to construct many cross-module Convex refs. This weakens type safety and violates the compliance requirements to avoid string-based ref factories, limit unsafe casts, and prefer generated `api`/`internal` refs.

## Issue Context
The file currently justifies avoiding codegen, but the compliance checklist requires using generated refs by default and only using `makeFunctionReference(&quot;module:function&quot;)` as a localized workaround at confirmed TS2589 hotspots.

## Fix Focus Areas
- packages/convex/convex/automationHttpRoutes.ts[32-79]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Idempotency race duplicates 🐞 Bug ✓ Correctness
Description
sendMessageIdempotent is vulnerable to concurrent requests using the same Idempotency-Key: both
can miss the initial lookup and create separate messages before inserting their idempotency rows.
This breaks the endpoint’s idempotency guarantee and can create duplicate outbound messages on
client retries.
Code

packages/convex/convex/automationApiInternals.ts[R595-679]

+    // Check idempotency key if provided
+    if (args.idempotencyKey) {
+      const existing = await ctx.db
+        .query("automationIdempotencyKeys")
+        .withIndex("by_workspace_key", (q) =>
+          q.eq("workspaceId", args.workspaceId).eq("key", args.idempotencyKey!)
+        )
+        .first();
+
+      if (existing && existing.expiresAt >= Date.now()) {
+        return { cached: true, result: existing.responseSnapshot };
+      }
+    }
+
+    // Perform the message send (same logic as sendMessageForAutomation)
+    const conv = await ctx.db.get(args.conversationId);
+    if (!conv || conv.workspaceId !== args.workspaceId) {
+      throw new Error("Conversation not found");
+    }
+
+    const claim = await ctx.db
+      .query("automationConversationClaims")
+      .withIndex("by_conversation_status", (q) =>
+        q.eq("conversationId", args.conversationId).eq("status", "active")
+      )
+      .first();
+
+    if (!claim || claim.credentialId !== args.credentialId) {
+      throw new Error("No active claim for this conversation. Claim the conversation first.");
+    }
+
+    if (claim.expiresAt < Date.now()) {
+      throw new Error("Claim has expired. Renew or re-claim the conversation.");
+    }
+
+    const now = Date.now();
+    const messageId = await ctx.db.insert("messages", {
+      conversationId: args.conversationId,
+      senderId: `automation:${args.actorName}`,
+      senderType: "bot",
+      content: args.content,
+      automationCredentialId: args.credentialId,
+      createdAt: now,
+    });
+
+    await ctx.db.patch(args.conversationId, {
+      updatedAt: now,
+      lastMessageAt: now,
+      unreadByVisitor: (conv.unreadByVisitor || 0) + 1,
+    });
+
+    await ctx.db.patch(claim._id, {
+      expiresAt: now + 5 * 60 * 1000,
+    });
+
+    await logAudit(ctx, {
+      workspaceId: args.workspaceId,
+      actorType: "api",
+      action: "automation.message.sent",
+      resourceType: "message",
+      resourceId: String(messageId),
+      metadata: { credentialId: String(args.credentialId) },
+    });
+
+    await emitAutomationEvent(ctx, {
+      workspaceId: args.workspaceId,
+      eventType: "message.created",
+      resourceType: "message",
+      resourceId: messageId,
+      data: { conversationId: args.conversationId, senderType: "bot", channel: conv.channel ?? "chat" },
+    });
+
+    const result = { id: messageId };
+
+    // Store idempotency key if provided
+    if (args.idempotencyKey) {
+      await ctx.db.insert("automationIdempotencyKeys", {
+        workspaceId: args.workspaceId,
+        key: args.idempotencyKey,
+        credentialId: args.credentialId,
+        resourceType: "message",
+        resourceId: String(messageId),
+        responseSnapshot: result,
+        expiresAt: now + IDEMPOTENCY_TTL_MS,
+      });
Evidence
The implementation does a non-atomic check-then-act: it first queries automationIdempotencyKeys
for (workspaceId,key) and, if not found, inserts a new message and only afterwards inserts the
idempotency row. Because the schema only defines a non-unique index (no uniqueness enforcement),
concurrent mutations can both observe “no row” and both insert, producing duplicates.

packages/convex/convex/automationApiInternals.ts[595-607]
packages/convex/convex/automationApiInternals.ts[630-679]
packages/convex/convex/schema/automationTables.ts[94-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`sendMessageIdempotent` implements idempotency as a read-then-write pattern. Under concurrent requests with the same `Idempotency-Key`, both can see no existing key and both create a message before writing the idempotency row, producing duplicates.

### Issue Context
Convex indexes do not enforce uniqueness. When the initial lookup returns no documents, the mutation reads no documents, so concurrent mutations can both proceed without conflicts.

### Fix Focus Areas
- packages/convex/convex/automationApiInternals.ts[583-684]
- packages/convex/convex/schema/automationTables.ts[94-110]

### Implementation direction
- Introduce a deterministic, conflict-inducing “lock”/reservation mechanism for `(workspaceId, key)` before creating the message (e.g., reserve an idempotency record first and make concurrent requests conflict on a shared document), then patch it with `responseSnapshot` after success.
- Ensure subsequent requests:
 - return the stored `responseSnapshot` when present and unexpired
 - do **not** create a new message when a reservation exists for the same key

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Workspace rate-limit bypass 🐞 Bug ⛯ Reliability
Description
checkRateLimit can create multiple automationWorkspaceRateLimits rows for the same workspace
under concurrent first requests, and later requests read an arbitrary .first() row. This makes
workspace-level rate limiting unreliable and can allow exceeding the intended per-workspace cap.
Code

packages/convex/convex/lib/automationAuth.ts[R154-175]

+    // 1. Check workspace-level rate limit first (120 req/min)
+    const wsRateLimit = await ctx.db
+      .query("automationWorkspaceRateLimits")
+      .withIndex("by_workspace", (q) => q.eq("workspaceId", args.workspaceId))
+      .first();
+
+    if (wsRateLimit) {
+      if (now > wsRateLimit.windowStart + RATE_LIMIT_WINDOW_MS) {
+        await ctx.db.patch(wsRateLimit._id, { windowStart: now, count: 1 });
+      } else if (wsRateLimit.count >= WORKSPACE_RATE_LIMIT) {
+        const retryAfter = Math.ceil((wsRateLimit.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000);
+        return { allowed: false, retryAfter };
+      } else {
+        await ctx.db.patch(wsRateLimit._id, { count: wsRateLimit.count + 1 });
+      }
+    } else {
+      await ctx.db.insert("automationWorkspaceRateLimits", {
+        workspaceId: args.workspaceId,
+        windowStart: now,
+        count: 1,
+      });
+    }
Evidence
The mutation queries the first rate-limit row by workspace and inserts a new one if none exists;
concurrent requests can both take the insert branch. The schema only defines an index by workspace,
not any uniqueness constraint, so multiple documents per workspace are possible and .first() will
ignore the others.

packages/convex/convex/lib/automationAuth.ts[154-175]
packages/convex/convex/schema/automationTables.ts[106-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Workspace-level rate limiting can be bypassed because `checkRateLimit` inserts a new `automationWorkspaceRateLimits` doc when none exists; under concurrency, multiple docs can be created, and later `.first()` reads only one of them.

### Issue Context
Convex indexes do not enforce uniqueness. If multiple rows exist for the same workspace, the code does not reconcile them.

### Fix Focus Areas
- packages/convex/convex/lib/automationAuth.ts[141-175]
- packages/convex/convex/schema/automationTables.ts[106-110]

### Implementation direction
- Make the rate-limit row a true singleton per workspace:
 - create it at workspace provisioning time (preferred), or
 - store counters on the `workspaces` document itself, or
 - add a dedicated singleton table keyed by workspace with a conflict-inducing update path.
- If existing duplicates are possible, add reconciliation/cleanup logic (pick one canonical row and delete/merge others).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Test ping not targeted 🐞 Bug ✓ Correctness
Description
testSubscription emits a generic test.ping event through the normal event fan-out, so the
selected subscription may receive nothing if its filters exclude test.ping/webhook, and other
active subscriptions may receive the ping instead. This contradicts the documented behavior of
sending a test ping to the subscription URL.
Code

packages/convex/convex/automationWebhooks.ts[R149-168]

+export const testSubscription = authMutation({
+  args: {
+    workspaceId: v.id("workspaces"),
+    subscriptionId: v.id("automationWebhookSubscriptions"),
+  },
+  permission: "settings.integrations",
+  handler: async (ctx, args) => {
+    const sub = await ctx.db.get(args.subscriptionId);
+    if (!sub || sub.workspaceId !== args.workspaceId) {
+      throw new Error("Subscription not found");
+    }
+
+    // Emit a test event
+    await ctx.scheduler.runAfter(0, emitEventRef as any, {
+      workspaceId: args.workspaceId,
+      eventType: "test.ping",
+      resourceType: "webhook",
+      resourceId: args.subscriptionId,
+      data: { test: true, timestamp: Date.now() },
+    });
Evidence
The admin ‘test ping’ schedules automationEvents:emitEvent, which enumerates all active
subscriptions and applies eventTypes/resourceTypes/etc filters before creating deliveries. Since
subscriptions can be configured with filters, the tested subscription can be filtered out; and the
event is not scoped to only the chosen subscription. The docs explicitly describe test ping as
sending to the subscription URL.

packages/convex/convex/automationWebhooks.ts[149-168]
packages/convex/convex/automationWebhooks.ts[23-31]
packages/convex/convex/automationEvents.ts[56-91]
docs/api-reference.md[560-566]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`testSubscription` currently emits a normal automation event (`test.ping`) which is then fanned out by `emitEvent` to all active subscriptions subject to their filters. This means the tested subscription can be filtered out and unrelated subscriptions can receive the ping.

### Issue Context
Subscriptions can define `eventTypes`/`resourceTypes` filters; `emitEvent` enforces them for all events.

### Fix Focus Areas
- packages/convex/convex/automationWebhooks.ts[149-172]
- packages/convex/convex/automationEvents.ts[56-95]

### Implementation direction
- In `testSubscription`, bypass `emitEvent` and instead:
 - create an `automationEvents` row (optional) for observability, and
 - insert an `automationWebhookDeliveries` row **for `args.subscriptionId` only**, then
 - `runAfter(0, deliverWebhook, { deliveryId })`.
- Ensure the test delivery ignores subscription filters (since it is an explicit admin action).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +35 to +37
const fn = (name: string) => makeFunctionReference(name) as any;

const listConversationsRef = fn("automationApiInternals:listConversationsForAutomation");

Choose a reason for hiding this comment

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

Action required

1. fn builds untyped refs 📘 Rule violation ✓ Correctness

The new fn(name: string) helper creates Convex refs from arbitrary strings via
makeFunctionReference(name) as any, weakening type safety and hardening guarantees. This also
applies makeFunctionReference("module:function") broadly rather than preferring generated
api/internal refs.
Agent Prompt
## Issue description
`packages/convex/convex/automationHttpRoutes.ts` introduces a string-based ref factory (`const fn = (name: string) => makeFunctionReference(name) as any;`) and uses it broadly to construct many cross-module Convex refs. This weakens type safety and violates the compliance requirements to avoid string-based ref factories, limit unsafe casts, and prefer generated `api`/`internal` refs.

## Issue Context
The file currently justifies avoiding codegen, but the compliance checklist requires using generated refs by default and only using `makeFunctionReference("module:function")` as a localized workaround at confirmed TS2589 hotspots.

## Fix Focus Areas
- packages/convex/convex/automationHttpRoutes.ts[32-79]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +595 to +679
// Check idempotency key if provided
if (args.idempotencyKey) {
const existing = await ctx.db
.query("automationIdempotencyKeys")
.withIndex("by_workspace_key", (q) =>
q.eq("workspaceId", args.workspaceId).eq("key", args.idempotencyKey!)
)
.first();

if (existing && existing.expiresAt >= Date.now()) {
return { cached: true, result: existing.responseSnapshot };
}
}

// Perform the message send (same logic as sendMessageForAutomation)
const conv = await ctx.db.get(args.conversationId);
if (!conv || conv.workspaceId !== args.workspaceId) {
throw new Error("Conversation not found");
}

const claim = await ctx.db
.query("automationConversationClaims")
.withIndex("by_conversation_status", (q) =>
q.eq("conversationId", args.conversationId).eq("status", "active")
)
.first();

if (!claim || claim.credentialId !== args.credentialId) {
throw new Error("No active claim for this conversation. Claim the conversation first.");
}

if (claim.expiresAt < Date.now()) {
throw new Error("Claim has expired. Renew or re-claim the conversation.");
}

const now = Date.now();
const messageId = await ctx.db.insert("messages", {
conversationId: args.conversationId,
senderId: `automation:${args.actorName}`,
senderType: "bot",
content: args.content,
automationCredentialId: args.credentialId,
createdAt: now,
});

await ctx.db.patch(args.conversationId, {
updatedAt: now,
lastMessageAt: now,
unreadByVisitor: (conv.unreadByVisitor || 0) + 1,
});

await ctx.db.patch(claim._id, {
expiresAt: now + 5 * 60 * 1000,
});

await logAudit(ctx, {
workspaceId: args.workspaceId,
actorType: "api",
action: "automation.message.sent",
resourceType: "message",
resourceId: String(messageId),
metadata: { credentialId: String(args.credentialId) },
});

await emitAutomationEvent(ctx, {
workspaceId: args.workspaceId,
eventType: "message.created",
resourceType: "message",
resourceId: messageId,
data: { conversationId: args.conversationId, senderType: "bot", channel: conv.channel ?? "chat" },
});

const result = { id: messageId };

// Store idempotency key if provided
if (args.idempotencyKey) {
await ctx.db.insert("automationIdempotencyKeys", {
workspaceId: args.workspaceId,
key: args.idempotencyKey,
credentialId: args.credentialId,
resourceType: "message",
resourceId: String(messageId),
responseSnapshot: result,
expiresAt: now + IDEMPOTENCY_TTL_MS,
});

Choose a reason for hiding this comment

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

Action required

2. Idempotency race duplicates 🐞 Bug ✓ Correctness

sendMessageIdempotent is vulnerable to concurrent requests using the same Idempotency-Key: both
can miss the initial lookup and create separate messages before inserting their idempotency rows.
This breaks the endpoint’s idempotency guarantee and can create duplicate outbound messages on
client retries.
Agent Prompt
### Issue description
`sendMessageIdempotent` implements idempotency as a read-then-write pattern. Under concurrent requests with the same `Idempotency-Key`, both can see no existing key and both create a message before writing the idempotency row, producing duplicates.

### Issue Context
Convex indexes do not enforce uniqueness. When the initial lookup returns no documents, the mutation reads no documents, so concurrent mutations can both proceed without conflicts.

### Fix Focus Areas
- packages/convex/convex/automationApiInternals.ts[583-684]
- packages/convex/convex/schema/automationTables.ts[94-110]

### Implementation direction
- Introduce a deterministic, conflict-inducing “lock”/reservation mechanism for `(workspaceId, key)` before creating the message (e.g., reserve an idempotency record first and make concurrent requests conflict on a shared document), then patch it with `responseSnapshot` after success.
- Ensure subsequent requests:
  - return the stored `responseSnapshot` when present and unexpired
  - do **not** create a new message when a reservation exists for the same key

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +154 to +175
// 1. Check workspace-level rate limit first (120 req/min)
const wsRateLimit = await ctx.db
.query("automationWorkspaceRateLimits")
.withIndex("by_workspace", (q) => q.eq("workspaceId", args.workspaceId))
.first();

if (wsRateLimit) {
if (now > wsRateLimit.windowStart + RATE_LIMIT_WINDOW_MS) {
await ctx.db.patch(wsRateLimit._id, { windowStart: now, count: 1 });
} else if (wsRateLimit.count >= WORKSPACE_RATE_LIMIT) {
const retryAfter = Math.ceil((wsRateLimit.windowStart + RATE_LIMIT_WINDOW_MS - now) / 1000);
return { allowed: false, retryAfter };
} else {
await ctx.db.patch(wsRateLimit._id, { count: wsRateLimit.count + 1 });
}
} else {
await ctx.db.insert("automationWorkspaceRateLimits", {
workspaceId: args.workspaceId,
windowStart: now,
count: 1,
});
}

Choose a reason for hiding this comment

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

Action required

3. Workspace rate-limit bypass 🐞 Bug ⛯ Reliability

checkRateLimit can create multiple automationWorkspaceRateLimits rows for the same workspace
under concurrent first requests, and later requests read an arbitrary .first() row. This makes
workspace-level rate limiting unreliable and can allow exceeding the intended per-workspace cap.
Agent Prompt
### Issue description
Workspace-level rate limiting can be bypassed because `checkRateLimit` inserts a new `automationWorkspaceRateLimits` doc when none exists; under concurrency, multiple docs can be created, and later `.first()` reads only one of them.

### Issue Context
Convex indexes do not enforce uniqueness. If multiple rows exist for the same workspace, the code does not reconcile them.

### Fix Focus Areas
- packages/convex/convex/lib/automationAuth.ts[141-175]
- packages/convex/convex/schema/automationTables.ts[106-110]

### Implementation direction
- Make the rate-limit row a true singleton per workspace:
  - create it at workspace provisioning time (preferred), or
  - store counters on the `workspaces` document itself, or
  - add a dedicated singleton table keyed by workspace with a conflict-inducing update path.
- If existing duplicates are possible, add reconciliation/cleanup logic (pick one canonical row and delete/merge others).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +149 to +168
export const testSubscription = authMutation({
args: {
workspaceId: v.id("workspaces"),
subscriptionId: v.id("automationWebhookSubscriptions"),
},
permission: "settings.integrations",
handler: async (ctx, args) => {
const sub = await ctx.db.get(args.subscriptionId);
if (!sub || sub.workspaceId !== args.workspaceId) {
throw new Error("Subscription not found");
}

// Emit a test event
await ctx.scheduler.runAfter(0, emitEventRef as any, {
workspaceId: args.workspaceId,
eventType: "test.ping",
resourceType: "webhook",
resourceId: args.subscriptionId,
data: { test: true, timestamp: Date.now() },
});

Choose a reason for hiding this comment

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

Action required

4. Test ping not targeted 🐞 Bug ✓ Correctness

testSubscription emits a generic test.ping event through the normal event fan-out, so the
selected subscription may receive nothing if its filters exclude test.ping/webhook, and other
active subscriptions may receive the ping instead. This contradicts the documented behavior of
sending a test ping to the subscription URL.
Agent Prompt
### Issue description
`testSubscription` currently emits a normal automation event (`test.ping`) which is then fanned out by `emitEvent` to all active subscriptions subject to their filters. This means the tested subscription can be filtered out and unrelated subscriptions can receive the ping.

### Issue Context
Subscriptions can define `eventTypes`/`resourceTypes` filters; `emitEvent` enforces them for all events.

### Fix Focus Areas
- packages/convex/convex/automationWebhooks.ts[149-172]
- packages/convex/convex/automationEvents.ts[56-95]

### Implementation direction
- In `testSubscription`, bypass `emitEvent` and instead:
  - create an `automationEvents` row (optional) for observability, and
  - insert an `automationWebhookDeliveries` row **for `args.subscriptionId` only**, then
  - `runAfter(0, deliverWebhook, { deliveryId })`.
- Ensure the test delivery ignores subscription filters (since it is an explicit admin action).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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