calcom/cal.diy · error · ConflictException

Webhook with this subscriber url already exists for this oAu

Error message

Webhook with this subscriber url already exists for this oAuth client

What it means

OAuthClientWebhooksService.createOAuthClientWebhook queries getOAuthClientWebhookByUrl(platformOAuthClientId, subscriberUrl); if a row already exists it throws ConflictException (HTTP 409). The (platformOAuthClientId, subscriberUrl) pair is unique per OAuth client.

Source

Thrown at apps/api/v2/src/modules/webhooks/services/oauth-clients-webhooks.service.ts:18

import { PipedInputWebhookType } from "@/modules/webhooks/pipes/WebhookInputPipe";
import { validateWebhookUrl } from "@/modules/webhooks/utils/validate-webhook-url";
import { WebhooksRepository } from "@/modules/webhooks/webhooks.repository";
import { ConflictException, Injectable } from "@nestjs/common";

@Injectable()
export class OAuthClientWebhooksService {
  constructor(private readonly webhooksRepository: WebhooksRepository) {}

  async createOAuthClientWebhook(platformOAuthClientId: string, body: PipedInputWebhookType) {
    validateWebhookUrl(body.subscriberUrl);

    const existingWebhook = await this.webhooksRepository.getOAuthClientWebhookByUrl(
      platformOAuthClientId,
      body.subscriberUrl
    );
    if (existingWebhook) {
      throw new ConflictException("Webhook with this subscriber url already exists for this oAuth client");
    }

    return this.webhooksRepository.createOAuthClientWebhook(platformOAuthClientId, {
      ...body,
      payloadTemplate: body.payloadTemplate ?? null,
      secret: body.secret ?? null,
    });
  }

  async getOAuthClientWebhooksPaginated(platformOAuthClientId: string, skip: number, take: number) {
    return this.webhooksRepository.getOAuthClientWebhooksPaginated(platformOAuthClientId, skip, take);
  }

  async deleteAllOAuthClientWebhooks(platformOAuthClientId: string): Promise<{ count: number }> {
    return this.webhooksRepository.deleteAllOAuthClientWebhooks(platformOAuthClientId);
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. List the client's existing webhooks and check the subscriberUrl before creating.
  2. Make subscriber URLs unique per registration.
  3. Handle 409 as success-when-duplicate.

Example fix

// before
await api.createOAuthClientWebhook(clientId, { subscriberUrl, ... });
// after
const list = await api.listOAuthClientWebhooks(clientId);
if (list.find(w => w.subscriberUrl === subscriberUrl)) return;
await api.createOAuthClientWebhook(clientId, { subscriberUrl, ... });
Defensive patterns

Strategy: validation

Validate before calling

async function createIfMissingOAuthClient(api, clientId, body) {
  const list = await api.getOAuthClientWebhooksPaginated(clientId, 0, 1000);
  if (list.find(w => w.subscriberUrl === body.subscriberUrl)) return;
  return api.createOAuthClientWebhook(clientId, body);
}

Type guard

const isDuplicateUrl = (list: { subscriberUrl: string }[], url: string): boolean =>
  list.some(w => w.subscriberUrl === url);

Try / catch

try { await api.createOAuthClientWebhook(clientId, body); }
catch (e) { if (e.status === 409) return; else throw e; }

Prevention

When it happens

Trigger: POSTing a webhook for an OAuth client whose subscriberUrl is already registered for that same client. Unlike event-type/user webhooks, there is no DELEGATION_CREDENTIAL_ERROR guard here — only the duplicate check.

Common situations: Re-registering a callback after a transient failure that actually succeeded; two OAuth flows for the same client using one callback URL; replayed webhooks.

Related errors


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