---
title: Rotate SIP credentials without dropping a single active call
description: >-
  How to rotate SIP credentials safely using a two-phase
  create-verify-drain-revoke pattern that keeps in-flight calls alive
  throughout.
summary: >-
  Revoking a SIP credential while calls are active drops those calls instantly.
  This post explains the two-phase rotation pattern and the drain window that
  prevents it.
date: 2026-07-04T00:00:00.000Z
type: blog
---


## Summary

Rotating a SIP credential without a drain window drops every in-flight call the moment the old credential is revoked. The safe approach is a two-phase pattern: create the new credential, provision it on your SIP client, wait for all active calls to complete, then revoke the old one. This post explains exactly how to implement that pattern using the Sautikit API.

## Sautikit SIP Credential Model: What a Credential Controls

A Sautikit SIP credential is a username/password pair bound to a specific phone number in your workspace. When your SIP client (a softphone, a PBX, or a media server) registers with Sautikit's SIP infrastructure, it authenticates using this credential. All calls placed or received through that number are associated with the credential that registered for it.

Credentials are scoped to a number, not to a workspace. This means you can have multiple numbers in one workspace with independent credential rotation schedules. Rotating the credential on `+254712345678` has no effect on calls active on `+254711000000`.

You can retrieve the current SIP credentials for a number:

```bash
curl -s https://api.sautikit.com/v1/numbers/num_abc123/sip-credentials \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY"
```

```json
{
  "id": "sipcred_01J2K3M4N5P6Q7R8S9T0",
  "number_id": "num_abc123",
  "username": "ts_01J2K3M4N5P6Q7R8S9T0",
  "realm": "a8f1b9c2-4e3d-4a5b-9c8d-7e6f5a4b3c2d",
  "status": "active",
  "created_at": "2026-04-01T08:00:00Z"
}
```

The `realm` is your account UUID. It is stable across credential rotations: the realm identifies the account, not the credential. Hardcoding the realm in your SIP client configuration is an anti-pattern: it couples your client configuration to the account's internal identifier. Extract it from the `WWW-Authenticate` header on first registration instead:

```
WWW-Authenticate: Digest realm="a8f1b9c2-4e3d-4a5b-9c8d-7e6f5a4b3c2d", nonce="...", algorithm=MD5
```

## The Danger Zone: Why Immediate Revocation Drops Live Calls

SIP uses Digest authentication (RFC 3261 §22.4). The authentication lifecycle works like this:

1. Your SIP client sends a `REGISTER` or `INVITE` request.
2. Sautikit responds with `401 Unauthorized` carrying a `WWW-Authenticate` header containing a nonce.
3. Your client sends a second request with a `Authorization: Digest` header containing the computed response.
4. Sautikit validates the credential and returns `200 OK`.

The credential is authenticated at the **session establishment** step. Once a call is active (post-`200 OK` for the `INVITE`), the credential is not re-checked on every in-flight packet. The call's media session uses the already-authenticated context.

What happens on revocation is this: the next `REGISTER` or `re-INVITE` from your SIP client will be rejected with `403 Forbidden`. For active calls, this matters because many SIP clients send periodic `re-INVITE` packets for session keepalive or quality negotiation. If the credential is gone when that `re-INVITE` arrives, Sautikit sends `403` and the client tears down the call.

To see this in a SIP trace, capture with:

```bash
tcpdump -i any -w sip.pcap port 5060
```

Then open in Wireshark and filter `sip`. You will see the `re-INVITE` and immediately after, the `403 Forbidden` response if the credential was revoked mid-call.

## Two-Phase Rotation: Create, Verify, Drain, Revoke

The safe pattern has four steps:

### Phase 1: Create the new credential

```bash
curl -s -X POST https://api.sautikit.com/v1/numbers/num_abc123/sip-credentials \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rotate-$(date +%Y%m%d)" \
  -d '{}'
```

```json
{
  "id": "sipcred_NEW01J2K3M4N5P6",
  "number_id": "num_abc123",
  "username": "ts_NEW01J2K3M4N5P6",
  "password": "rnd_9X8w7v6u5t4s3r2q",
  "realm": "a8f1b9c2-4e3d-4a5b-9c8d-7e6f5a4b3c2d",
  "status": "active"
}
```

The `password` is returned only on creation. Store it in your secrets manager immediately; it is not recoverable after this response.

### Phase 2: Provision the new credential on your SIP client

Update your SIP client configuration (or your secrets manager reference) to use the new username and password. Trigger a `REGISTER` and confirm authentication succeeds before proceeding:

```bash
# Using sipsak to verify registration
sipsak -s sip:ts_NEW01J2K3M4N5P6@sip.sautikit.com \
  --password "rnd_9X8w7v6u5t4s3r2q"
# Should return: SIP/2.0 200 OK
```

### Phase 3: Drain the old credential

Do **not** revoke the old credential yet. Instead, wait for all calls active on it to complete. Query the in-progress calls:

```bash
curl -s "https://api.sautikit.com/v1/calls?status=in-progress&number_id=num_abc123" \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY"
```

Wait until the response shows `"total": 0`. The drain window must be at least equal to your p99 call duration. For Kenyan fintech voice OTP flows, this is typically 45–90 seconds. For conference calls, it can be 90 minutes. Measure your p99 before setting the window:

```bash
# Get the 99th percentile call duration for the past 7 days
curl -s "https://api.sautikit.com/v1/calls?number_id=num_abc123&created_after=2026-06-27T00:00:00Z&limit=1000" \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY" \
  | jq '[.calls[].duration_seconds] | sort | .[length * 0.99 | floor]'
```

### Phase 4: Revoke the old credential

Once the drain confirms zero active calls:

```bash
curl -s -X DELETE \
  "https://api.sautikit.com/v1/numbers/num_abc123/sip-credentials/sipcred_01J2K3M4N5P6Q7R8S9T0" \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY"
```

```json
{ "status": "revoked" }
```

The old credential is now invalid. Any `REGISTER` attempts using it will receive `403 Forbidden`.

## Automating Rotation with the Sautikit API and a Cron Job

This shell script implements the full two-phase rotation and can be run as a cron job:

```bash
#!/usr/bin/env bash
# rotate-sip-credential.sh
# Usage: ./rotate-sip-credential.sh num_abc123

set -euo pipefail

NUMBER_ID="${1:?usage: $0 <number_id>}"
BASE="https://api.sautikit.com"
HEADERS=(-H "Authorization: Bearer $SAUTIKIT_API_KEY")
DRAIN_POLL_INTERVAL=15  # seconds between active-call checks
DRAIN_MAX_WAIT=7200     # 2 hours maximum drain window

echo "==> Creating new SIP credential for $NUMBER_ID"
NEW_CRED=$(curl -sf -X POST "$BASE/v1/numbers/$NUMBER_ID/sip-credentials" \
  "${HEADERS[@]}" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rotate-${NUMBER_ID}-$(date +%Y%m%d)" \
  -d '{}')

NEW_ID=$(echo "$NEW_CRED" | jq -r '.id')
NEW_USER=$(echo "$NEW_CRED" | jq -r '.username')
NEW_PASS=$(echo "$NEW_CRED" | jq -r '.password')

echo "==> New credential: $NEW_ID (username: $NEW_USER)"

# Determine the old credential ID (assumes exactly one pre-existing credential).
OLD_ID=$(curl -sf "$BASE/v1/numbers/$NUMBER_ID/sip-credentials" \
  "${HEADERS[@]}" | jq -r ".credentials[] | select(.id != \"$NEW_ID\") | .id" | head -1)

echo "==> Old credential: $OLD_ID"
echo "==> Update your SIP client to use username=$NEW_USER, then press ENTER."
read -r

echo "==> Waiting for active calls to drain..."
elapsed=0
while true; do
  active=$(curl -sf "$BASE/v1/calls?status=in-progress&number_id=$NUMBER_ID" \
    "${HEADERS[@]}" | jq '.total')
  if [[ "$active" -eq 0 ]]; then
    echo "==> No active calls. Proceeding to revocation."
    break
  fi
  echo "    $active active call(s) remaining. Waiting ${DRAIN_POLL_INTERVAL}s..."
  sleep "$DRAIN_POLL_INTERVAL"
  elapsed=$((elapsed + DRAIN_POLL_INTERVAL))
  if [[ "$elapsed" -ge "$DRAIN_MAX_WAIT" ]]; then
    echo "ERROR: drain window exceeded ${DRAIN_MAX_WAIT}s. Aborting." >&2
    exit 1
  fi
done

echo "==> Revoking old credential: $OLD_ID"
curl -sf -X DELETE "$BASE/v1/numbers/$NUMBER_ID/sip-credentials/$OLD_ID" \
  "${HEADERS[@]}"

echo "==> Rotation complete."
```

## Testing Rotation in Staging: Simulating a Live Call

In your staging environment, place a long-duration call (using `Record` with no timeout as the voice action, so the call stays active), then run the rotation script. Verify that:

1. The new credential registers successfully while the old credential is still active.
2. The drain loop detects the in-progress call and waits.
3. The call completes naturally (or you manually end it via the dashboard).
4. The drain loop sees zero active calls and proceeds to revocation.
5. The old credential's next `REGISTER` returns `403`.

## Monitoring: the Metric to Watch During the Drain Window

During the drain, watch the active call count metric in the Sautikit dashboard alongside your SIP client's registration status. What you want to see:

- New credential: `REGISTERED`
- Old credential: `REGISTERED` (still authenticating, sustaining in-flight calls)
- Active calls on the number: decreasing toward zero

The moment you see `active_calls = 0` and both credentials are `REGISTERED`, it is safe to revoke. If the new credential shows `REGISTRATION_FAILED` at any point, halt the rotation and debug before proceeding; revoking the old credential with the new one broken would cut all call capacity.

## Get started

1. [Create a Sautikit workspace](/) and claim a phone number.
2. Top up over **M-Pesa**: KES billing, no card.
3. Provision a SIP credential on your number and wire up the two-phase drain-and-revoke rotation before your next credential change.

**[Start with Sautikit →](/)** &nbsp;·&nbsp; **[See pricing →](/pricing)** &nbsp;·&nbsp; **[Need SMS, WhatsApp & an agent desk? Helloduty →](https://helloduty.com)**

## Related resources

- [Phone numbers concepts](/developers/concepts/numbers)
- [Calls API reference](/developers/api#calls)
- [Pricing: number rental and minutes](/pricing)
- [RFC 3261 SIP §22: Authentication](https://www.rfc-editor.org/rfc/rfc3261#section-22)
- [OWASP Cryptographic Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html)
