GetUserForCallHandler
What It Does
When CallCore receives a call segment from the phone system, it needs to link the call's handler (a PhoneCallHandler__c record) to a Salesforce User. This link is what enables the timeline to show only calls handled by users the running user is permitted to see, and what drives the "own call" permission checks.
The default implementation matches the handler to a User by email address. If the email sent by the phone system matches exactly one active Salesforce User, that user is linked. If there is no match or the match is ambiguous, the handler is left unlinked.
Implement GetUserForCallHandler.IStrategy to change how this matching works — for example, to match by extension number, by a custom external ID field on User, or to integrate with a directory service.
Interface
global interface IStrategy {
void tryGet(Context context);
}
Context
Input Fields
All input fields are global final — they are set when the call segment arrives and cannot be changed by the strategy.
| Field | Type | Description |
|---|---|---|
email |
String |
Email address of the call handler as reported by the phone system. May be null or blank. |
displayName |
String |
Cleaned/normalised display name. May be null or blank. |
rawDisplayName |
String |
Display name exactly as received from the phone system. May be null or blank. |
extensionNumber |
Integer |
Extension number as reported by the phone system. May be null. |
callcoreWorkspaceSubdomain |
String |
The CallCore workspace subdomain the event originated from. Useful for multi-tenant setups. |
Write Methods
Call exactly one of these before returning:
| Method | Description |
|---|---|
found(Id userId) |
Records the Salesforce User ID that was matched. userId must not be null. |
notFound() |
Records that no match was found. The handler remains unlinked. |
If you call neither method, the result is the same as notFound().
Discriminator
This strategy has no SObject discriminator. There is one global implementation per org. Use default as the discriminator value.
Default Behaviour
Matches by email address. Queries User WHERE IsActive = true AND Email = :email. If exactly one user is found, calls context.found(userId). If zero or more than one user is found, calls nothing (effectively notFound()).
Example: Match by Extension Number
If your phone system identifies users by extension and you store extensions on a custom field PhoneExtension__c on the User object:
public class GetUserForCallHandlerByExtension
implements callcoreio.GetUserForCallHandler.IStrategy {
public void tryGet(callcoreio.GetUserForCallHandler.Context context) {
if (context.extensionNumber == null) {
context.notFound();
return;
}
String ext = String.valueOf(context.extensionNumber);
List<User> users = [
SELECT Id
FROM User
WHERE IsActive = true
AND PhoneExtension__c = :ext
LIMIT 2
];
if (users.size() == 1) {
context.found(users[0].Id);
} else {
context.notFound();
}
}
}
Register with:
- Strategy Type:
GetUserForCallHandler - Discriminator:
default - Apex Class Name:
GetUserForCallHandlerByExtension
Example: Try Extension First, Fall Back to Email
public class GetUserForCallHandlerByExtensionOrEmail
implements callcoreio.GetUserForCallHandler.IStrategy {
public void tryGet(callcoreio.GetUserForCallHandler.Context context) {
if (context.extensionNumber != null) {
String ext = String.valueOf(context.extensionNumber);
List<User> users = [
SELECT Id FROM User
WHERE IsActive = true AND PhoneExtension__c = :ext
LIMIT 2
];
if (users.size() == 1) {
context.found(users[0].Id);
return;
}
}
if (!String.isBlank(context.email)) {
String email = context.email.trim();
List<User> users = [
SELECT Id FROM User
WHERE IsActive = true AND Email = :email
LIMIT 2
];
if (users.size() == 1) {
context.found(users[0].Id);
return;
}
}
context.notFound();
}
}
Notes
- Always query with
LIMIT 2when checking for uniqueness. This lets you distinguish "no match" from "ambiguous match" without scanning the full User table. - Avoid linking a handler to a user when the match is ambiguous — an incorrect link will cause calls to appear for the wrong user. Calling
notFound()is safer than guessing. - This strategy is invoked once per handler per sync event, not on every timeline load. The result is stored on
PhoneCallHandler__c.User__c. If you change your matching logic, existing handlers already linked to users are not automatically re-evaluated. - The
callcoreWorkspaceSubdomainfield is useful if your org handles calls from multiple CallCore workspaces with different user populations.