Every business that takes calls eventually gets calls it doesn’t want — pranksters, spam, repeat harassers. We run a cloud contact center for our clients, and one of the features they rely on is the ability to block a caller. This is the story of a feature that sounds trivial (“just block the number”) but hides a trap that can take a client’s entire phone line offline if you get it wrong.
The basic feature
Clients tell us which numbers they never want to hear from. We keep that list, and when a call comes in we check it right inside the call flow. If the caller is on the client’s block list, we don’t ring an agent — we play a short message (“the number you reached is not available”) and hang up. Simple, fast, and it happens before a human is ever involved.
The twist: call forwarding
Here’s where it gets interesting.
In the normal case, when caller 123 dials client 999, our contact center sees the call as coming from 123, arriving at the client’s line 111. We know exactly who is calling, so blocking works perfectly.
But some clients forward their calls to us. When client 999 turns on call forwarding, the caller’s real number is lost along the way. Instead of seeing caller 123, our contact center sees the call coming from 999 — the client’s own forwarding line.
sequenceDiagram
participant C as Caller (123)
participant Cl as Client line (999)
participant CC as Contact center
participant A as Agent (111)
Note over C,A: Normal call — we see the real caller
C->>CC: call, from = 123
CC->>A: from 123 → 111 ✅ real caller
Note over C,A: Forwarded call — the caller is hidden
C->>Cl: call
Cl-->>CC: forwarded, from = 999
CC->>A: from 999 → 111 ⚠️ client's own line
Now the trap is obvious. If a client ever tries to block the number 999 — maybe by mistake, maybe because a spam call looked like it came from there — we would be blocking their own forwarding line. Every forwarded call would hit the block and get disconnected. The client’s phone would effectively go dark, and it would look like our fault.
So the real problem was never “how do we block a number.” It was “how do we refuse to block a number that is actually a client’s forwarding line.”
What didn’t work
Our telephony provider offers a “call was forwarded” indicator attribute, and our first instinct was to just read it. It didn’t hold up. Not every client’s setup passes the original caller’s number along with the forwarding, so the indicator was present for some calls and missing for others. We couldn’t trust it as the basis for a decision that, if wrong, breaks a client’s business. We needed something that worked regardless of how each client had configured their phones.
What worked: a three-step block decision
Instead of trusting one signal, we built a small decision service. Before we ever honor a block request, it runs three checks. The guiding principle is conservative: if any check even hints that the number might be a legitimate forwarding line, we do not block it. We would rather leave one spam number unblocked than take a paying client’s line down.
flowchart LR
A[Block request] --> S1{One of our own<br/>contact-center numbers?}
S1 -- yes --> N[Do NOT block]
S1 -- no --> S2{In the account's known<br/>forwarding-line list?}
S2 -- yes --> N
S2 -- no --> S3{Recent calls confirm<br/>the real caller?}
S3 -- yes / no history --> B[BLOCK]
S3 -- looks forwarded --> N
Step 1 — Is it one of our own numbers? We first check whether the number the client wants to block is actually one of the numbers we own inside the contact center. If so, blocking it would be self-sabotage, so we stop immediately and don’t block.
Step 2 — Is it a known forwarding line? We keep a per-account list of numbers that look like the client’s forwarding lines (more on how that list is built below). If the number is on that list, we don’t block it.
Step 3 — Do recent calls prove it’s a real caller? If the first two checks pass, we look at this caller’s recent call history to the client. When a caller reaches us, we record the number they report as theirs. If that reported number matches the number our contact center actually saw on every recent call, then we really are seeing the original caller — not a forwarding line — and it’s safe to block. If the two ever disagree, the number is behaving like a forwarding line, and we don’t block. And if there’s no recent history at all, there’s no evidence it’s a forwarding line, so we allow the block.
If a client has several numbers, “do not block” wins the tie: it only takes one of them looking like a forwarding line for us to hold off.
How the pieces fit together
Three parts work together, each doing one job:
flowchart TD
P[Client portal] -->|block / unblock| G[Block-decision service]
D[Forwarding-line detector<br/>background job] -->|writes list| ACC[(Account details)]
G -->|reads list| ACC
G -->|reads call history| H[(Call records)]
G -->|checks our numbers| T[Telephony provider]
- Client portal — where clients add and remove blocked callers. It’s just a friendly front end over our blocking API.
- Forwarding-line detector — a background job that periodically studies each client number’s call history and flags likely forwarding lines. The tell-tale pattern is a number that relays many calls from very few distinct origins — exactly how a forwarding line behaves, and not how a normal caller does. It saves that list onto the client’s account, ready for Step 2 above.
- Block-decision service — the piece that runs the three checks and answers a single question: is this number safe to block? It also handles the plain block/unblock operations, plus a whitelist (“never block this one, no matter what”) as an explicit escape hatch.
Where we go next
The system does its job today, but we see clear next steps — including some honest limitations we already know about:
- Get the list from the source. Right now we infer a client’s forwarding lines from their call history. If clients simply told us their forwarding lines directly, we’d skip a lot of analysis and compute. That’s the biggest planned improvement.
- Use external spam data. We’d like to bring in third-party spam and fraud scoring so we can flag known bad callers before a client even asks.
- Known limitations we want to fix. The recent-history check looks at a sample of calls rather than every single one, so it’s probabilistic — a rare forwarding pattern could slip past it. And a caller with no recent history currently defaults to “block,” which is the safe choice for spam but something we’d like to make smarter over time.
The lesson we took away: the hard part of “block this number” wasn’t the blocking. It was knowing, reliably, whose number it really is.