TL;DR: When a caller says, "Use this number," an inbound voice agent needs a usable calling number and the caller's agreement to use it. I classify the carrier-supplied value before the assistant starts, then inject an available or withheld branch. The assistant uses that branch to choose its question. It does not decide whether the raw caller-ID looks like a phone number.
This guide is for Voice AI builders using Vapi and Make.com who want a message-taking agent to collect a callback number without assuming the calling number is available.
It expands the second note in From the Dry Dock, my notes on building, testing and improving Voice AI. Mira is the example assistant in the conversations below. The startup flow and response fields come from my inbound Vapi and Make.com customer-number resolver.
Why does "use this number" need more than a prompt instruction?
Our message-taking agent collects a caller name, a callback number and a message. The callback number sounds like a straightforward field until the caller answers with a reference instead of digits.
Mira: What number would you like to be called back on?
Caller: Use this number.
The caller means the number they are calling from. On an inbound phone call, the agent can only reuse the caller-ID that reached Vapi through the telephony carrier. I have had calls where the caller asked for a callback on that number, but the carrier passed anonymous instead of digits.
The caller's intention was clear. The number was missing.
A browser voice call has no carrier caller-ID to reuse. A mobile, a landline and a browser call can produce the same spoken request while giving the assistant different information.
I therefore separate two questions:
Does this call provide a number that our resolver accepts as usable?
Does the caller want that number used for the callback?
Code answers the first. The conversation answers the second.
What does Vapi's customer.number variable tell us?
Vapi exposes {{customer.number}} in assistant prompts and messages. Its Variables documentation distinguishes the calling number on an inbound call from the destination on an outbound call.
That distinction matters. This guide concerns inbound message-taking. An outbound destination is a different piece of call context.
The default variable gives us a value to inspect. It does not supply our callback policy. An empty value, an unresolved template or a carrier placeholder cannot fulfil "use this number" just because it occupies the customer-number field.
I use a resolver, a small piece of code that classifies the value, to make that decision before the assistant begins. The prompt receives a named result instead of having to interpret the raw string.
What counts as usable in this resolver?
The resolver trims surrounding whitespace and checks the call type and number against explicit rules. It accepts two shapes:
An international-looking number: a leading
+followed by 7 to 15 digits. This is the resolver's E.164-shaped check, rather than a complete check of the numbering plan.A local number: digits and allowed phone punctuation, containing at least 7 digits. The allowed characters are digits, whitespace, hyphens, parentheses, full stops and
+.
These are the recognisers used in the Make.com JavaScript:
const E164_PATTERN = /^\+\d{7,15}$/;
const LOCAL_PHONE_CHARS_PATTERN = /^[\d\s\-().+]+$/;
function isDigitBearingLocalPhone(value) {
if (!value || !LOCAL_PHONE_CHARS_PATTERN.test(value)) return false;
return value.replace(/\D/g, "").length >= 7;
}They are a shape check. They do not prove that the number is assigned, reachable, owned by the caller or ready for an outbound dialler. A local number still needs the correct country context if a later system converts it to an international dialling format.
There is also a limitation in the current local-number rule: it has no upper digit limit and permits + among its allowed characters. Passing that rule should not be described as strict E.164 validation. A stricter dialling requirement needs a separate normalisation and validation step.
The resolver takes the withheld path when:
The call type is
webCall, even if a number-shaped value is present.The number is empty or contains only whitespace.
The value contains the unresolved literal
{{customer.number}}or{{ customer.number }}.The value is
anonymous,private,withheld,restricted,unknownorunavailable, ignoring letter case.The value fails both accepted number shapes.
Here, withheld is the internal name for the unavailable path. It does not establish that the caller deliberately hid their number. Missing data, a web call and an invalid value all lead to the same conversation choice.
One Make.com detail needs care: build the unresolved-template comparison strings at runtime so Make does not expand them inside the Code module. The blueprint uses this form, with a second version for the spaced literal:
const UNRESOLVED_LITERAL = ["{", "{", "customer.number", "}", "}"].join("");
const UNRESOLVED_LITERAL_SPACED = ["{", "{", " customer.number ", "}", "}"].join("");How does Make.com resolve the number before the assistant starts?
Use Vapi's inbound assistant-request flow to run the resolver before returning the assistant to start. Vapi documents this approach in Personalization with user information.
The call sequence is:
The caller rings the Vapi phone number.
Vapi sends an
assistant-requestto the configured server endpoint. In this build, Make.com's Custom webhook receives it.A Make Code module reads the carrier-supplied number and call type, then classifies them.
Make builds a JSON response containing the saved assistant ID and the resolved variable values.
The Webhook response module returns HTTP 200 with
Content-Type: application/json.Vapi starts the assistant with the branch already available in its prompt.

Configure the inbound phone number to use the server-selection flow rather than a static assistant or squad assignment. Simply adding a resolver URL to a mid-call tool does not establish this startup sequence.
In the Make blueprint, the extractor reads message.customer, falling back to message.call.customer, and reads the type from message.call.type. Inspect your own inbound payload before mapping these fields. The number should come from the provider's request, rather than model-generated tool arguments.
An illustrative response for an available number looks like this. Replace the assistant ID with your saved assistant's ID; the number shown is an example.
{
"assistantId": "YOUR_SAVED_ASSISTANT_ID",
"assistantOverrides": {
"variableValues": {
"customerNumberBranch": "available",
"customerNumberState": "valid_e164",
"customerNumberUsable": true,
"customerNumberRaw": "+442012345678"
}
}
}customerNumberBranch controls the conversation. The other fields explain the classification and retain the supplied value. The existing resolver labels accepted local numbers valid_e164 too, so that state label is informational and should not be used as proof of international formatting.
Vapi's Server events documentation specifies a fixed 7.5-second end-to-end deadline for an assistant-request response. Keep classification small and return promptly. A prompt rule for missing variables cannot rescue a call whose startup webhook never returns.
What should the assistant do with the injected branch?
The assistant should take the available path only when {{customerNumberBranch}} resolves to the exact value available.
Every other value takes the unavailable path: withheld, a blank, a missing variable, an unresolved template literal or an unexpected result. This is what I mean by failing closed here. Uncertainty means asking for a number rather than suggesting that the assistant already has one.
The following prompt fragment expresses that policy. It illustrates the callback-number section; it is not a complete assistant prompt.
Choose the callback-number path using {{customerNumberBranch}} only.
Use the available path only for the exact resolved value available.
For any other value, use the unavailable path.
Available path:
Ask: "Is this the number you would like to be called back on?"
Use the calling number only if the caller agrees.
If they want another number, ask them to provide it.
Unavailable path:
Ask: "The number you are calling from is unavailable.
What number would you like to be called back on?"
If they say "use this number", explain that it is not available
and ask them to give the number they want used.
Do not classify {{customer.number}} yourself.
Do not choose the path from the state, usable flag or raw number.
Do not call, wait for or retry a resolver tool during the conversation.The code makes the availability decision deterministic. The assistant's spoken behaviour still needs testing. A resolved variable and a correct prompt do not, by themselves, prove that every conversation will follow the intended path.
How should the two conversations sound?
For the unavailable path, explain the limitation without implying that the caller deliberately withheld their number.
Mira: The number you are calling from is unavailable. What number would you like to be called back on?
Caller: Use this number.
Mira: That number is not available to me. Please give me the number you want us to call you back on.
For the available path, offer the calling number as a choice.
Mira: Is this the number you would like to be called back on?
Caller: Yes. Use this number.
Mira: I will use the number you are calling from.
These are intended dialogue examples. The second exchange establishes a choice; it does not test whether a later callback will connect. If the caller supplies a different number, the assistant needs to capture and confirm that number through its ordinary number-taking procedure.
What should you test before relying on this flow?
Test the classifier separately from the conversation. These representative cases match the resolver rules:
International-shaped number:
+442012345678on an inbound phone call returnsavailable.Local number:
02088469022returnsavailableunder the local-number rule.Phone punctuation:
(020) 8846 9022returnsavailableunder the same rule.Carrier placeholders: each of the six listed placeholders returns
withheld. Include mixed case, such asAnonymous.Empty value: an empty string or whitespace returns
withheld.Unresolved template: both documented literal forms return
withheld.Browser call:
webCallreturnswithheld, including when a number-shaped value is supplied.Invalid value:
not-a-number, a six-digit value or letters mixed with digits returnswithheld.
Then inspect a real inbound webhook request and response. Confirm the correct fields reach Make, the response selects your intended assistant, and variableValues contains the exact branch before the assistant starts.
Finally, exercise the conversations:
With an available number, agree to use it, then repeat the call and request a different number.
With an unavailable number, say "use this number" and check that the assistant asks for digits instead of claiming to have them.
Supply a missing, unresolved or unexpected branch in a controlled test. Check that the assistant uses the unavailable question.
Check that the assistant does not invoke the old resolver tool during the conversation.
Classifier tests show what the code returns. An inbound test checks startup wiring. Listening to the call checks the conversation. Each answers a different question.
FAQ
Does available mean the caller has confirmed the callback number?
No. It means the resolver accepted the supplied value. The caller must still choose that number. Availability and confirmation are separate decisions.
Can a browser caller say "use this number"?
They can say it, but this resolver treats webCall as unavailable because there is no carrier caller-ID to reuse. Ask them to supply the callback number.
Why keep customerNumberRaw if the assistant branches on a different field?
The raw value is the candidate number to use after the caller agrees. The branch tells the assistant whether it may offer that candidate. Keeping both avoids asking the assistant to reconstruct a number from the classification result.
Does this cover outbound calls too?
This guide covers inbound message-taking. On outbound calls, Vapi's customer-number variable represents the destination. Decide the callback policy for that flow explicitly before reusing these conversation rules.
Where does this leave callback-number capture?
The resolver answers whether there is a number to offer. The caller decides whether it is the number they want used. Keeping those decisions separate gives Mira a clear question for both cases.
In my earlier Dry Dock note on message capture, I covered the caller name, callback number and message, and why their meaning still needs confirmation. "Use this number" can be accepted only when the agent has an available candidate and the caller has chosen it.




