sautikit
PricingDevelopersBlogAbout
Sign inStart building
Guides
  • Browser Calling with WebRTC
  • Build a Call Center with Conferences
  • Build a Voice IVR
  • Claim and Route a Number
  • Quickstart: Place a Call
  • Record and Stream to S3
  • Verify Webhook Signatures
Concepts
  • Browser Calling with WebRTC
  • Calls
  • Phone Numbers
  • Voice Actions DSL
  • Wallet and Billing
  • Webhooks
  • Workspaces
Voice Actions
  • Conference
  • Dial
  • GetDigits
  • Hangup
  • Play
  • Record
  • Redirect
  • Reject
  • Say
Webhooks
  • call.answered
  • call.completed
  • call.failed
  • call.recording.ready
  • call.started
  • number.provisioned
  • number.released
  • storage.tier_changed
  • wallet.low_balance
  • wallet.top_up
SDKs
  • Go SDK
  • Node.js SDK
  • PHP SDK
  • Python SDK
Errors
  • account.suspended
  • accounts.admin_required
  • accounts.last_owner
  • accounts.remove_denied
  • accounts.role_change_denied
  • accounts.write_denied
  • api_key.already_revoked
  • api_key.expired
  • api_key.invalid
  • api_key.name_required
  • api_key.not_found
  • api_key.revoked
  • api_key.scope_denied
  • api_key.scope_invalid
  • audit.invalid_action_kind
  • auth.admin_required
  • auth.cannot_unlink_only_auth
  • auth.email_taken
  • auth.google_exchange_failed
  • auth.google_not_configured
  • auth.google_state_mismatch
  • auth.invite_email_mismatch
  • auth.no_workspace
  • auth.session_invalid
  • auth.token_expired
  • auth.token_invalid
  • auth.token_used
  • auth.workspace_forbidden
  • calls.events_failed
  • calls.get_failed
  • calls.list_failed
  • calls.not_found
  • calls.recording_expired
  • calls.recording_failed
  • calls.recording_not_found
  • calls.stats_failed
  • currency.unsupported
  • idempotency.conflict
  • internal_error
  • invitations.not_found
  • invite_requires_login
  • method_not_allowed
  • mpesa.unavailable
  • not_found
  • numbers.already_claimed
  • numbers.invalid_routing_url
  • numbers.not_assigned
  • numbers.not_found
  • numbers.price_missing
  • numbers.retired
  • numbers.series_invalid
  • numbers.series_not_active
  • numbers.sip_config_unavailable
  • numbers.sip_credentials.already_revoked
  • numbers.sip_credentials.not_found
  • numbers.sip_token_unavailable
  • numbers.suffix_collision
  • numbers.wallet_unavailable
  • paystack.unavailable
  • pbx.body_read_failed
  • pbx.ingest_failed
  • pbx.payload_decode_failed
  • pbx.resolve_failed
  • pbx.unknown_workspace
  • provider.has_active_series
  • provider.invalid_credentials
  • storage.tier_invalid
  • storage.tier_switch_failed
  • topup.not_found
  • validation.bad_request
  • validation.invalid_cursor
  • wallet.adjustment_invalid_kind
  • wallet.alert_subscriber_exists
  • wallet.alert_subscriber_invalid_email
  • wallet.alert_subscriber_not_found
  • wallet.alert_subscribers_max
  • wallet.currency_mismatch
  • wallet.insufficient_funds
  • wallet.invalid_amount
  • wallet.not_found
  • wallet.topup_below_minimum
  • webhooks.create_failed
  • webhooks.delete_failed
  • webhooks.deliver.secret_unset
  • webhooks.delivery_not_retryable
  • webhooks.dispatcher_unavailable
  • webhooks.event_unsupported
  • webhooks.list_failed
  • webhooks.not_found
  • webhooks.patch_failed
  • webhooks.rotate_secret_failed
Changelog
  • 2026-06-15: Public Pricing Endpoint
  • 2026-06-20: WebRTC SIP Token (HS256 5-min TTL + Key Rotation)
  • 2026-06-27: sautikit.com Launch
  • Inbound voice is now free; outbound drops to KES 3/min

wallet.low_balance

Fires when the workspace wallet balance drops below the configured alert threshold.

2026-06-27

Next Steps

  • WebhooksSautikit webhooks deliver signed JSON payloads to your HTTPS endpoint. Delivery is at-least-once with an 8-attempt exponential backoff schedule. Verify the HMAC-SHA256 signature on every request.
sautikit

Programmable voice infrastructure for Africa. Buy numbers, place calls, and bill per second, all in KES, via API.

Product

NumbersCalls & routingRecordingsWallet & billingPricing

Developers

DocumentationAPI referenceQuickstartAI promptChangelog

Company

AboutBlogCareersConsole

© 2026 Sautikit. All rights reserved.

Sautikit provides voice API services for application developers. Numbers provisioned on this platform are not configured for emergency calling (e.g. 999 / 112). Do not use Sautikit numbers as a replacement for a primary phone line.

Summary

wallet.low_balance fires when a wallet debit brings the balance below the workspace's configured alert threshold. It is emitted at most once per threshold-crossing event; subsequent debits while already below the threshold do not re-fire until the balance recovers above the threshold and drops again.

This event is subscribable via workspace webhooks (POST /v1/webhooks).

Payload

{
  "kind": "wallet.low_balance",
  "event_id": "01900000-0000-7000-8000-000000000001",
  "workspace_id": "01900000-0000-7000-8000-000000000002",
  "occurred_at": "2026-06-27T10:00:00.000Z",
  "data": {
    "balance_minor": 4800,
    "currency": "KES",
    "threshold_minor": 5000
  }
}

Headers

Every webhook delivery includes the following request headers:

HeaderDescription
X-Sautikit-SignatureHMAC-SHA256 of the raw body, hex-encoded. Verify with your subscription secret.
X-Sautikit-Idempotency-KeyUnique delivery ID for deduplication.
X-Sautikit-EventLiteral event kind: wallet.low_balance.

Delivery semantics

  • At-least-once: The dispatcher may deliver the same event more than once. Deduplicate using event_id or the X-Sautikit-Idempotency-Key header.
  • Retry schedule: Up to 5 attempts with exponential backoff (1-2-4-8-16 seconds). After exhausting attempts the delivery row moves to dead_letter.
  • Timeout: Your endpoint must respond within 10 seconds. A non-2xx response or timeout counts as a failed attempt.

Example handler

import { createHmac } from "node:crypto";
 
export async function POST(req) {
  const sig = req.headers["x-sautikit-signature"];
  const body = await req.text();
  const expected = createHmac("sha256", process.env.WEBHOOK_SECRET)
    .update(body)
    .digest("hex");
  if (sig !== expected) return new Response("Forbidden", { status: 403 });
 
  const event = JSON.parse(body);
  if (event.kind === "wallet.low_balance") {
    const { balance_minor, currency, threshold_minor } = event.data;
    console.warn(`Low balance: ${balance_minor / 100} ${currency} (threshold: ${threshold_minor / 100})`);
    // trigger top-up, send Slack alert, page on-call...
  }
  return new Response("OK", { status: 200 });
}

Next steps

  • Subscribe via POST /v1/webhooks with events: ["wallet.low_balance"].
  • Configure alert email subscribers via POST /v1/wallet/alert/subscribers.
  • Pair with wallet.top_up to clear alerts when funds are replenished.