Skip to main content

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


image.png




Nieuw script:

AI Rate Limiter

Type:

When a user dials a dial code

Dial Code:

5554
Script:

#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


image.png


Naam:

AI Gateway

Forwarding:

Forward all calls
→ External Number
→ 5555


image.png



Overflow

Gebruik in de Ring Group of Queue:

No Answer Destination
→ 5554


image.png


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.