Automatic rotation applies to Microsoft 365 delegation credentials only. Google Workspace delegation credentials authenticate with a service account key that doesn’t expire, so there’s nothing to rotate.
What you need
- Organization admin access in Cal.com
- Microsoft Entra admin access for the tenant that owns the app registration, in a work or school tenant (this setup cannot be completed with a personal/consumer Microsoft account)
- The Azure CLI
Step 1: Grant the rotation permission
Cal.com rotates the secret by calling Microsoft GraphaddPassword on your app registration, which requires the Application.ReadWrite.OwnedBy application permission.
1
Add the permission
In the Microsoft Entra admin center, open your app registration, go to API permissions → Add a permission → Microsoft Graph → Application permissions, and add
Application.ReadWrite.OwnedBy.2
Grant admin consent
Click Grant admin consent for <your tenant> and confirm that the permission shows as granted.
Step 2: Make the app an owner of itself
Application.ReadWrite.OwnedBy only lets the application manage app registrations it owns. Granting the permission is not enough on its own — the application’s own service principal has to be an owner of its app registration. This is the most common reason rotation looks configured but keeps failing.
The Owners blade in the portal only accepts users, so the ownership has to be added with the Azure CLI.
You need two different object IDs:
Step 3: Check your tenant secret lifetime policy
Cal.com creates each replacement secret with a 180-day lifetime. If your tenant has an app management policy that caps client secret lifetimes below 180 days, Microsoft rejects every attempt and rotation never succeeds. If that applies to you, either exempt this app registration from the policy, relax the cap to at least 180 days, or keep rotating the secret manually by opting the credential out of automatic rotation.Step 4: Make sure the credential isn’t opted out
Automatic rotation is on by default. If the credential was explicitly opted out, turn that off with API v2 — there’s no toggle for this in the dashboard yet:GET on the same endpoint shows the current value.
Step 5: Hand the current secret over to Cal.com
Cal.com can only rotate a secret it created itself — it has no way to learn the expiry date of a secret you pasted in. So a credential that has never had a Cal.com-managed secret needs one staged once, which happens when a credential is enabled:1
Disable the credential
Go to Settings → Organization → Delegation Credential and toggle the credential off.
2
Enable it again
Toggle it back on. Cal.com verifies the credential, then creates a Cal.com-managed secret on the app registration and stages it as pending — your own secret stays the active one, so nothing breaks in the meantime.
3
Wait for promotion
Within about an hour, Cal.com validates the pending secret with an app-only token request and promotes it to active. From then on, Cal.com knows the expiry and rotates it automatically about a week before it runs out.
If you’d rather replace the secret at the same time, updating the client secret through API v2 disables the credential as part of the update — enabling it again then stages the managed secret.
Step 6: Verify it worked
GET /v2/organizations/{orgId}/delegation-credentials/{credentialId} returns the rotation state:
secret.expiresAtfilled in and roughly 180 days out means a Cal.com-managed secret is active and rotation is set up.pendingSecret.createdAtset means a replacement is staged and waiting for validation.secretRotationErrorCodetells you why the last attempt failed — see below.
When rotation fails
Cal.com retries a failing rotation for 48 hours. After that it stops trying, setssecretRotationBlocked to true and fires a DELEGATION_CREDENTIAL_SECRET_ROTATION_FAILED webhook whose error.code is SECRET_MINT_FAILED (Microsoft refused to create the replacement) or SECRET_PROMOTION_FAILED (the replacement was created but never authenticated successfully).
Treat any value you don’t recognize as
unknown — new codes can be added over time.
Once you’ve fixed the underlying problem, clear the block from Settings → Organization → Delegation Credential → ⋯ → Reset rotation (only selectable while a credential is blocked), or with POST /v2/organizations/{orgId}/delegation-credentials/{credentialId}/reset-rotation. Rotation then resumes on the next scheduled run.
Audit rotations with webhooks
Three webhook triggers cover the rotation lifecycle:DELEGATION_CREDENTIAL_SECRET_ROTATED— a managed secret was validated and promoted to active. Useful as an audit trail.DELEGATION_CREDENTIAL_SECRET_ROTATION_FAILED— rotation reached a terminal failure and the credential is blocked. Subscribe to this if you rely on automatic rotation.DELEGATION_CREDENTIAL_ROTATION_REQUIRED— sent for credentials that opted out of automatic rotation, as a reminder to rotate manually.