calcom/cal.diy · error · ForbiddenException

IsUserWebhookGuard - user with id=(${user.id}) is not the ow

Error message

IsUserWebhookGuard - user with id=(${user.id}) is not the owner of webhook with id=(${webhookId})

What it means

IsUserWebhookGuard fetched the webhook by id and found that webhook.userId does not equal the authenticated user's id, and the user is not a system admin. The guard enforces per-user ownership of webhooks; only the owner or a system admin may proceed. It throws ForbiddenException (HTTP 403).

Source

Thrown at apps/api/v2/src/modules/webhooks/guards/is-user-webhook-guard.ts:28

  constructor(private readonly webhooksService: WebhooksService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<Request & { webhook: Webhook }>();
    const user = request.user as ApiAuthGuardUser;
    const webhookId = request.params.webhookId;

    if (!user) {
      throw new ForbiddenException("IsUserWebhookGuard - No user associated with the request.");
    }

    if (!webhookId) {
      throw new ForbiddenException("IsUserWebhookGuard - No webhook id found in request params.");
    }

    const webhook = await this.webhooksService.getWebhookById(webhookId);

    if (webhook.userId !== user.id && !user.isSystemAdmin) {
      throw new ForbiddenException(
        `IsUserWebhookGuard - user with id=(${user.id}) is not the owner of webhook with id=(${webhookId})`
      );
    }

    request.webhook = webhook;
    return true;
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Authenticate as the user who owns the webhook (the user whose id equals webhook.userId).
  2. If legitimate admin access is required, perform the call with a system-admin account.
  3. Verify the webhookId belongs to the current user via a list-my-webhooks call before operating on it.
  4. Audit the client's session/token handling to ensure the correct user context is sent.

Example fix

// before
await api.delete(`/webhooks/${webhookId}`); // wrong session
// after
const mine = await api.get('/webhooks');
if (!mine.find(w => w.id === webhookId)) throw new ForbiddenError('not owner');
await api.delete(`/webhooks/${webhookId}`);
Defensive patterns

Strategy: validation

Validate before calling

async function ensureOwnsWebhook(api, user, webhookId) {
  const mine = await api.listMyWebhooks();
  if (!mine.find(w => w.id === webhookId) && !user.isSystemAdmin) {
    throw new ForbiddenError(`user ${user.id} does not own ${webhookId}`);
  }
}

Type guard

const isWebhookOwner = (webhook: { userId: number }, user: { id: number; isSystemAdmin?: boolean }): boolean =>
  webhook.userId === user.id || user.isSystemAdmin === true;

Try / catch

try { await api.delete(`/webhooks/${id}`); }
catch (e) {
  if (e.status === 403 && /not the owner/.test(e.message)) { /* use owner session or admin */ }
  else throw e;
}

Prevention

When it happens

Trigger: An authenticated user calls a webhook endpoint (GET/PATCH/DELETE) for a webhook owned by a different user, while not having isSystemAdmin=true. The guard compares webhook.userId === user.id after loading the webhook.

Common situations: Cross-tenant access bug in the client (wrong user session reused); sharing a webhook id between team members who are not admins; a token from user A used to operate on user B's webhook; stale cached id.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/16cd47b0f6a64457. Report an issue: GitHub.