Call Script – AI Rate Limiter
Doel
Voorkomen dat één beller onbeperkt de ElevenLabs AI-agent kan gebruiken en daarmee onnodige kosten veroorzaakt.
De oplossing is gebouwd als een 3CX Call Script dat het aantal oproepen per Caller-ID controleert voordat een gesprek naar ElevenLabs wordt doorgestuurd.
Werking
Het script houdt per Caller-ID bij:
- Hoe vaak er is gebeld binnen een bepaalde periode.
- Of het nummer tijdelijk geblokkeerd is.
Configuratie:
Maximaal: 5 gesprekken
Periode: 15 minuten
Blokkade: 1 uur
Voorbeeld:
0237510860
Call 1 → toegestaan
Call 2 → toegestaan
Call 3 → toegestaan
Call 4 → toegestaan
Call 5 → toegestaan
Call 6 → geblokkeerd
Na 1 uur wordt het nummer automatisch vrijgegeven.
Configuratie
Script Type
Admin
→ Integrations
→ Call Scripts
Nieuw script:
AI Rate Limiter
Type:
When a user dials a dial code
Dial Code:
5554
#nullable disable
using CallFlow;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TCX.Configuration;
using TCX.PBXAPI;
namespace dummy
{
public class InterceptInboundCall : ScriptBase<InterceptInboundCall>
{
// TEST instellingen
const int MaxCalls = 5;
static readonly TimeSpan Window = TimeSpan.FromMinutes(15);
static readonly TimeSpan BlockTime = TimeSpan.FromHours(1);
// Toegestane calls gaan naar dummy extensie 556
// Extensie 556 forwardt daarna naar 5555 → outbound rule → ElevenLabs
const string AllowedDestination = "Extension.556.";
static readonly object LockObj = new object();
static readonly Dictionary<string, List<DateTime>> CallHistory = new Dictionary<string, List<DateTime>>();
static readonly Dictionary<string, DateTime> BlockedUntil = new Dictionary<string, DateTime>();
public override async Task<bool> StartAsync()
{
try
{
var callerId = MyCall.Caller?.CallerID ?? "unknown";
callerId = callerId.Trim();
if (string.IsNullOrWhiteSpace(callerId))
callerId = "unknown";
var now = DateTime.UtcNow;
bool blocked = false;
int currentCount = 0;
DateTime blockedUntil = DateTime.MinValue;
lock (LockObj)
{
// Controleer bestaande blokkade
if (BlockedUntil.TryGetValue(callerId, out blockedUntil))
{
if (blockedUntil > now)
{
blocked = true;
}
else
{
// Blokkade verlopen: vrijgeven en teller resetten
BlockedUntil.Remove(callerId);
if (CallHistory.ContainsKey(callerId))
CallHistory[callerId].Clear();
}
}
if (!blocked)
{
if (!CallHistory.ContainsKey(callerId))
CallHistory[callerId] = new List<DateTime>();
// Oude calls buiten het tijdvenster verwijderen
CallHistory[callerId] = CallHistory[callerId]
.Where(t => now - t <= Window)
.ToList();
// Deze call registreren
CallHistory[callerId].Add(now);
currentCount = CallHistory[callerId].Count;
// Limiet overschreden
if (currentCount > MaxCalls)
{
blockedUntil = now.Add(BlockTime);
BlockedUntil[callerId] = blockedUntil;
// Teller leegmaken zodra blokkade start
CallHistory[callerId].Clear();
blocked = true;
}
}
}
if (blocked)
{
MyCall.Info($"AI rate limiter: caller {callerId} BLOCKED until {blockedUntil:u}");
MyCall.Return(false);
return true;
}
MyCall.Info($"AI rate limiter: caller {callerId} allowed. Count={currentCount}/{MaxCalls}");
if (DestinationStruct.TryParse(AllowedDestination, out var destination))
{
var result = await MyCall.RouteToAsync(destination);
MyCall.Info($"AI rate limiter: caller {callerId} routed to {AllowedDestination}. Result={result}");
return true;
}
MyCall.Error($"AI rate limiter: invalid destination {AllowedDestination}");
MyCall.Return(false);
return true;
}
catch (Exception ex)
{
MyCall.Error($"AI rate limiter error: {ex}");
MyCall.Return(false);
return true;
}
}
}
}
Routing
Dummy extensie
Maak een extensie aan:
556
Naam:
AI Gateway
Forwarding:
Forward all calls
→ External Number
→ 5555
Overflow
Gebruik in de Ring Group of Queue:
No Answer Destination
→ 5554
Flow:
Ring Group
↓
Timeout
↓
5554
↓
AI Rate Limiter Script
↓
Extension 556
↓
5555
↓
Outbound Rule
↓
ElevenLabs
Productie instellingen
In het script:
const int MaxCalls = 5;
static readonly TimeSpan Window =
TimeSpan.FromMinutes(15);
static readonly TimeSpan BlockTime =
TimeSpan.FromHours(1);
Belangrijk
Deze versie gebruikt een interne geheugenlijst:
Caller-ID
Aantal oproepen
Blokkade tijd
Bij een herstart van:
3CX Services
Server
Script
worden de tellers gereset.
Voor de huidige toepassing is dit acceptabel, omdat het uitsluitend bedoeld is als bescherming tegen misbruik van de AI-agent.
Getest
Succesvol getest op:
3CX V20
ElevenLabs SIP Trunk
TCP Transport
Resultaat:
✅ Calls worden doorgelaten naar ElevenLabs
✅ Caller-ID blijft behouden
✅ Meer dan 5 calls binnen 15 minuten worden geblokkeerd
✅ Automatische vrijgave na 1 uur
✅ Geen aanpassingen nodig aan ElevenLabs zijde
Status: Werkend en actief in productie.



