calcom/cal.diy · error · ConflictException

Webhook with this subscriber url already exists for this eve

Error message

Webhook with this subscriber url already exists for this event type

What it means

EventTypeWebhooksService.createEventTypeWebhook queries getEventTypeWebhookByUrl(eventTypeId, subscriberUrl); if a row already exists with that URL for the same event type, it throws ConflictException (HTTP 409). The (eventTypeId, subscriberUrl) pair is treated as unique.

Source

Thrown at apps/api/v2/src/modules/webhooks/services/event-type-webhooks.service.ts:26

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

  async createEventTypeWebhook(eventTypeId: number, body: PipedInputWebhookType) {
    validateWebhookUrl(body.subscriberUrl);

    if (body.eventTriggers.includes(WebhookTriggerEvents.DELEGATION_CREDENTIAL_ERROR)) {
      throw new BadRequestException(
        "DELEGATION_CREDENTIAL_ERROR trigger is only available for organization webhooks"
      );
    }

    const existingWebhook = await this.webhooksRepository.getEventTypeWebhookByUrl(
      eventTypeId,
      body.subscriberUrl
    );
    if (existingWebhook) {
      throw new ConflictException("Webhook with this subscriber url already exists for this event type");
    }
    return this.webhooksRepository.createEventTypeWebhook(eventTypeId, {
      ...body,
      payloadTemplate: body.payloadTemplate ?? null,
      secret: body.secret ?? null,
    });
  }

  getEventTypeWebhooksPaginated(eventTypeId: number, skip: number, take: number) {
    return this.webhooksRepository.getEventTypeWebhooksPaginated(eventTypeId, skip, take);
  }

  async deleteAllEventTypeWebhooks(eventTypeId: number): Promise<{ count: number }> {
    return this.webhooksRepository.deleteAllEventTypeWebhooks(eventTypeId);
  }
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Before creating, call the paginated list endpoint and check whether the subscriberUrl is already registered for this event type.
  2. Use a unique subscriberUrl per webhook (e.g. append a unique path segment).
  3. Treat HTTP 409 as 'already exists' and continue idempotently instead of erroring.
  4. De-duplicate retry payloads by subscriberUrl.

Example fix

// before
await api.createEventTypeWebhook(eventTypeId, { subscriberUrl, ... });
// after
const existing = await api.listEventTypeWebhooks(eventTypeId);
if (existing.find(w => w.subscriberUrl === subscriberUrl)) return existing.find(w => w.subscriberUrl === subscriberUrl);
await api.createEventTypeWebhook(eventTypeId, { subscriberUrl, ... });
Defensive patterns

Strategy: validation

Validate before calling

async function createIfMissingEventType(api, eventTypeId, body) {
  const list = await api.getEventTypeWebhooksPaginated(eventTypeId, 0, 1000);
  if (list.find(w => w.subscriberUrl === body.subscriberUrl)) return;
  return api.createEventTypeWebhook(eventTypeId, body);
}

Type guard

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

Try / catch

try { await api.createEventTypeWebhook(eventTypeId, body); }
catch (e) {
  if (e.status === 409) { /* treat as already registered, continue */ }
  else throw e;
}

Prevention

When it happens

Trigger: POSTing a webhook with a subscriberUrl that already exists for the same eventTypeId. Replaying a successful request, retrying after a timeout, or two clients registering the same callback URL.

Common situations: Idempotency-unaware retry logic; multiple integrations pointing at the same callback endpoint; development where the same ngrok URL is reused across attempts.

Related errors


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