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
- List the client's existing webhooks and check the subscriberUrl before creating.
- Make subscriber URLs unique per registration.
- 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
- Use distinct callback URLs per OAuth client registration.
- Deduplicate retries by subscriberUrl.
- Idempotently absorb 409 responses.
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
- Webhook with this subscriber url already exists for this eve
- Webhook with this subscriber url already exists for this eve
- Webhook with this subscriber url already exists for this use
- Google Meet is already connected for this team.
- User with id=${userId} has already authorized client with id
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/b0e79ca59f8b7772.
Report an issue: GitHub.