Host CRM Implementation
What the Host CRM integration must implement on the postMessage channel infinity-crm-bridge. The Generic CRM interface handles all Element API calls — the Host CRM integration listens, responds, and posts events to the Generic CRM interface only.
For embedding the bridge application and session setup, see Host CRM Integration. For how the Generic CRM interface maps each message to Element API, see Generic CRM Protocol.
Optional reference (_template/)
_template/)The sample app in resources/zendesk_sample-app.zip includes src/app/crm/_template/ as an optional reference — follow it if helpful. You can also implement directly from the message examples in this document.
| Path (in sample ZIP) | Purpose |
|---|---|
src/app/crm/_template/ | Optional reference — example layout for message bridge, directory fetch, availability fetch, screen pop handler |
src/app/crm/core/ | Generic CRM interface — CRM-agnostic routing and Element API |
If you follow _template/, rename files (template-* → <vendor>-*), replace placeholder identifiers, and wire your host CRM SDK in the TODO sections. A complete reference implementation is in the same sample ZIP under src/app/crm/zendesk/ — compare when wiring dial-out, directory, omnichannel, and screen pop.
Rule: Host CRM integration code never imports crm/core. Talk to the Generic CRM interface only via window.postMessage on channel infinity-crm-bridge.
Suggested layout if following _template/:
crm/<your-host-crm>/
<vendor>-message-bridge.ts # message listener + RPC replies
<vendor>-message.types.ts # channel id + message shapes
<vendor>-host-crm.adapter.ts # createClient, detach teardown, dispose
<vendor>-directory.fetch.ts
<vendor>-agent-availability.fetch.ts # when omnichannel S or C
<vendor>-interaction-screen-pop.handler.ts
Bridge shell and Agent UI URL
In the sample app, the Angular shell (app.ts / app.html) loads Avaya Infinity Agent in a nested iframe. The iframe src comes from agentUiURL returned by your host CRM adapter’s loadAppSettings() (for Zendesk, that value is read from ZAF app Settings).
Host CRM embeds bridge app
→ shell (app.ts) calls loadAppSettings() → { agentUiURL }
→ iframe [src] = agentUiURL (Infinity Agent)
→ crm/core connects to Agent (Element API + postMessage)
→ your Host CRM integration handles infinity-crm-bridge messages
Bootstrap (sample shell)
Implement createClient() and loadAppSettings() on your host CRM adapter. Return { agentUiURL } with the Avaya Infinity Agent URL. Wire your adapter in current-host-crm.adapter.ts. The shell (app.ts) calls these methods at startup — you do not need to modify app.ts unless you change how the shell works.
The client argument is an opaque handle to your host CRM SDK (for Zendesk, ZAFClient.init()). app.ts passes it to loadAppSettings(client); your adapter uses it to read install settings and return agentUiURL.
This document focuses on the postMessage step. The shell, adapter layout, and how you resolve agentUiURL are sample conventions — you may change them in your own bridge application (different UI, pop-out only, another framework, another config source).
How each feature works in the Host CRM integration
Every integration feature follows the same pattern:
- The Generic CRM interface activates — posts a bind message or an RPC
request(only when that feature is enabled in CRM Configuration, except screen pop and directory). - Host CRM integration wires the CRM SDK — subscribe to an event, poll an API, or fetch data on demand.
- Host CRM integration posts back — push events (
voice:dialout,availability:changed) or reply to RPC (response).
You do not read CRM Configuration in the Host CRM integration. If the Generic CRM interface never sends a bind or RPC for a feature, you do not implement that path.
| Feature | Generic CRM interface → Host CRM integration (activate) | Host CRM integration must wire in CRM | Host CRM integration → Generic CRM interface (result) |
|---|---|---|---|
| Click-to-dial / consult | voice:bind | Subscribe to dial-out (CTI, OpenCTI, UI click handler, etc.) | voice:dialout, optionally voice:error |
| CRM directory | request + fetchDirectory | Fetch contact list from host CRM API | response with { name, phone }[] |
| Omnichannel | availability:bind | Subscribe to agent/presence status changes | availability:changed when status changes |
| Omnichannel (Infinity → Host CRM integration) | request + applyUnifiedStatusByName | Apply the requested status via host CRM API | response with { applied, echoStatusId?, … } |
| Screen pop | screenPop:interaction | Open record(s) in host CRM UI | (no postMessage reply) |
Message listener
Register before Avaya Infinity Agent loads. Ignore any message whose channel is not infinity-crm-bridge:
window.addEventListener('message', (event) => {
const data = event.data;
if (!data || data.channel !== 'infinity-crm-bridge') return;
switch (data.type) {
case 'request':
// handle RPC — always reply with type: 'response', same requestId
break;
case 'voice:bind':
case 'voice:unbind':
break;
case 'availability:bind':
case 'availability:unbind':
break;
case 'screenPop:interaction':
break;
case 'detach':
break;
}
});Use window.postMessage(message, '*') (or a restricted target origin in production) for all Host CRM integration → Generic CRM interface messages.
Message summary
| Direction | Type | When | Host CRM integration action |
|---|---|---|---|
| Host CRM integration → Generic CRM interface | detach | Page unload / teardown | Stop integration |
| Generic CRM interface → Host CRM integration | detach | Generic CRM interface teardown | Clean up listeners |
| Generic CRM interface → Host CRM integration | voice:bind / voice:unbind | Click-to-dial / consult enabled | Subscribe / unsubscribe to host CRM dial-out events |
| Host CRM integration → Generic CRM interface | voice:dialout | User clicks a phone number or contact | Start dial or consult |
| Host CRM integration → Generic CRM interface | voice:error | Dial-out failed | Report error (optional) |
| Generic CRM interface → Host CRM integration | screenPop:interaction | Every interaction update from Agent UI | Open matching CRM record(s) when payload contains your screen-pop data |
| Generic CRM interface → Host CRM integration | request | Directory or omnichannel RPC | Handle method, post response |
| Host CRM integration → Generic CRM interface | response | Reply to every request | Same requestId, payload or error |
| Generic CRM interface → Host CRM integration | availability:bind / availability:unbind | Omnichannel enabled | Start / stop reporting CRM availability |
| Host CRM integration → Generic CRM interface | availability:changed | CRM availability changes | Push snapshot to the Generic CRM interface |
Session
The Generic CRM interface reads the assigned CRM Configuration via Element API and binds enabled features internally after the session is ready.
Your Host CRM integration listens on channel infinity-crm-bridge and handles messages from the Generic CRM interface for active features (voice:bind, request, availability:bind, and so on). In the sample repository, the Generic CRM interface registers the session and binds features in-process; *-crm-feature-bindings.ts only posts detach on teardown (see _template/template-crm-feature-bindings.ts).
Host CRM integration posts — detach
detach{
"channel": "infinity-crm-bridge",
"type": "detach"
}Host CRM integration receives — detach
detachThe Generic CRM interface may post detach during its own teardown. Release voice subscriptions, availability listeners, and other handlers.
Click-to-dial and click-to-consult
Requires click-to-dial and/or click-to-consult enabled in CRM Configuration. The Generic CRM interface posts voice:bind only when those features are enabled. See Configuration.
You receive
{ "channel": "infinity-crm-bridge", "type": "voice:bind" }The Generic CRM interface sends this when click-to-dial and/or click-to-consult is enabled. Subscribe to your host CRM dial-out event (CTI, OpenCTI, custom UI click handler, etc.) and keep the subscription until voice:unbind or teardown.
{ "channel": "infinity-crm-bridge", "type": "voice:unbind" }Unsubscribe from dial-out events on teardown.
Your implementation
- On
voice:bind, register your CRM dial-out listener (one subscription per bind). - When the user clicks a phone number or contact, post
voice:dialout(see below). - On dial-out failure, optionally post
voice:error. - On
voice:unbindordetach, remove the listener.
You send — voice:dialout
voice:dialoutWhen the user selects a phone number or contact in the host CRM:
{
"channel": "infinity-crm-bridge",
"type": "voice:dialout",
"payload": { "phoneNumber": "+1234567890" }
}Accepted payload keys: phoneNumber, number, phone, tel, destination.
The Generic CRM interface decides click-to-dial vs click-to-consult from the current interaction state. The Host CRM integration does not pass a queue.
You send — voice:error (optional)
voice:error (optional){
"channel": "infinity-crm-bridge",
"type": "voice:error",
"payload": { "message": "Dial-out not allowed" }
}Screen pop
Not controlled by a CRM Configuration feature flag — configure workflow data on the interaction. See Feature Walkthroughs — Screen Pop.
There is no bind message. The Generic CRM interface listens to every onInteractionUpdated from Agent UI and posts screenPop:interaction with the Interaction snapshot (current interaction state at that update). It does not filter by CRM attachment — the Host CRM integration receives an update for each interaction change Agent UI reports.
You receive
{
"channel": "infinity-crm-bridge",
"type": "screenPop:interaction",
"payload": {
"id": "int-123",
"custom": {
"crm": {
"myCrm": { "ID": 45678 }
}
}
}
}The payload is the Element API Interaction object. Field names and nesting beyond what your workflow attaches are not fixed here — read whatever your Infinity workflow writes (for example record ids under custom or another path you define).
For multiple matches, the attachment may be an array:
"myCrm": [{ "ID": 45678 }, { "ID": 45679 }]There is no postMessage reply for screen pop.
Your implementation
-
Handle
screenPop:interactionin your message bridge. -
Check the payload for screen-pop data — do not assume every message contains CRM record references. Many updates have no screen-pop fields (call progress, wrap-up, unrelated interaction changes). If your attachment is missing or empty, return without opening anything.
-
When screen-pop data is present, resolve it to host CRM record(s) using your platform APIs (user id, contact id, case id, route/deep-link invoke, etc.).
-
Open the Host CRM UI — navigate to or focus the matching record. For multiple matches, open one tab or view per record (the Zendesk sample uses sequential
routeTowith a short delay between tabs). -
No postMessage reply — screen pop is fire-and-forget from the Generic CRM interface.
Workflow must attach CRM references to the interaction (Webhook or CRM connection integration → Update Interaction) before screen-pop data appears on an update. Until then, screenPop:interaction messages may arrive without anything useful for your handler.
CRM directory (RPC)
When an Infinity Element requests contacts, the Generic CRM interface calls the Host CRM integration over RPC. There is no bind message — the Host CRM integration fetches the list on demand when fetchDirectory arrives.
You receive — fetchDirectory
fetchDirectory{
"channel": "infinity-crm-bridge",
"type": "request",
"requestId": "1",
"method": "fetchDirectory"
}Your implementation
- Handle
requestwithmethod: "fetchDirectory"in your message bridge. - Call your host CRM API to retrieve contacts (implement in
*-directory.fetch.ts). - Map each contact to
{ name, phone }and post aresponsewith the samerequestId. - On failure, post
responsewitherrorinstead ofpayload.
The Generic CRM interface forwards the list to the Infinity Element — you do not post directory data proactively.
You send — response
response{
"channel": "infinity-crm-bridge",
"type": "response",
"requestId": "1",
"payload": [
{ "name": "Jane Smith", "phone": "+1234567890" },
{ "name": "John Doe", "phone": "+19876543210" }
]
}On failure:
{
"channel": "infinity-crm-bridge",
"type": "response",
"requestId": "1",
"error": "directory_unavailable"
}Implement contact retrieval in *-directory.fetch.ts (see _template/template-directory.fetch.ts).
Omnichannel
Requires omnichannel Synchronized or Complementary in CRM Configuration. The Generic CRM interface posts availability:bind only when omnichannel is enabled. See Configuration and Generic CRM Protocol — Omnichannel.
Omnichannel is two-way. The Host CRM integration must implement both directions:
| Direction | Trigger | Host CRM integration work | Host CRM integration posts |
|---|---|---|---|
| Host CRM integration → Infinity | availability:bind | Subscribe to agent/presence status changes | availability:changed when status changes |
| Infinity → Host CRM integration | request + applyUnifiedStatusByName | Write the requested status via host CRM API | response with apply result |
You receive — bind / unbind
{ "channel": "infinity-crm-bridge", "type": "availability:bind" }The Generic CRM interface sends this when omnichannel S or C is enabled. Subscribe to agent status / presence changes — the same idea as dial-out on voice:bind.
{ "channel": "infinity-crm-bridge", "type": "availability:unbind" }Unsubscribe on teardown.
Your implementation — CRM status → Generic CRM interface
- On
availability:bind, subscribe to agent status changes in your host CRM. - When status changes, post
availability:changedwith{ id?, name?, reason? }mapped from your CRM presence model. - On
availability:unbindordetach, remove the subscription. - Implement read helpers in
*-agent-availability.fetch.ts(see_template/template-agent-availability.fetch.ts).
You send — availability:changed
availability:changedWhen CRM availability changes:
{
"channel": "infinity-crm-bridge",
"type": "availability:changed",
"payload": {
"id": 1,
"name": "Available",
"reason": "Ready"
}
}id, name, and reason map from your host CRM presence model. The Generic CRM interface maps these to Infinity agent state.
You receive — apply status (Infinity → Host CRM integration)
{
"channel": "infinity-crm-bridge",
"type": "request",
"requestId": "3",
"method": "applyUnifiedStatusByName",
"args": { "name": "Busy" }
}Triggered when Infinity agent state changes, when an interaction is accepted (Busy), or when the last interaction ends (restore previous state). See Generic CRM Protocol.
Your implementation — Infinity status → Host CRM integration
- Handle
requestwithmethod: "applyUnifiedStatusByName"in your message bridge. - Resolve the display name to your CRM's status id (if needed) and PUT/PATCH the agent status via your host CRM API.
- Post
responsewith the samerequestId— includeechoStatusIdwhen the nextavailability:changedyou push will echo that id (helps Generic CRM interface avoid sync loops). - Implement write helpers in
*-agent-availability.fetch.ts.
You send — response
response{
"channel": "infinity-crm-bridge",
"type": "response",
"requestId": "3",
"payload": {
"applied": true,
"echoStatusId": 2,
"resolvedName": "Busy"
}
}| Field | Purpose |
|---|---|
applied | true if the host CRM applied the requested status |
echoStatusId | Optional — set when the next availability:changed will echo this id (helps avoid sync loops) |
resolvedName | Optional — actual status name applied in the Host CRM |
failureReason | Optional — when applied is false |
Implement status read/write in *-agent-availability.fetch.ts (see _template/template-agent-availability.fetch.ts).
Active interactions and Busy
During an active interaction, the Generic CRM interface requests Busy on the host CRM (default name "Busy"). Your integration applies that status via the CRM API — it does not set Busy on its own. Ensure Busy exists in your CRM status catalog. When the last interaction ends, the Generic CRM interface requests the previous agent state label again via applyUnifiedStatusByName. While any interaction is active, host → Infinity sync is paused — availability:changed from the CRM does not update Agent UI until the interaction completes.
Integration tips
- Align status names — In Synchronized mode, the Generic CRM interface matches by display name. Use the same labels in CRM Configuration and your CRM status catalog (for example Available, Busy, Offline).
- Prefer native events — Subscribe to your CRM's status-change API on
availability:bind. Do not poll unless the CRM provides no event mechanism. - Use stable ids — When your CRM exposes a status id, include it in
availability:changedand return it asechoStatusIdon apply when the next push will echo that id. - Always respond to RPCs — Every
applyUnifiedStatusByNamerequest must get oneresponsewith the samerequestId. A missing or mismatched response can stall omnichannel updates for the session. - Set
appliedfrom the CRM outcome — Returnapplied: false(and optionalfailureReason) when the CRM API failed or the requested name does not exist. Returnapplied: trueonly when the CRM applied the requested status — not when a different status was written. resolvedNameis optional metadata — The Generic CRM interface does not use it for sync; ongoing state followsavailability:changed.- Test both directions — Change status in the CRM, change status in Infinity, accept and end an interaction (Busy override), and log out (Offline on CX logout in Synchronized mode).
- Complementary mode — Same postMessage surface (
availability:bind,availability:changed,applyUnifiedStatusByName). Mapping rules differ and run in the Generic CRM interface; see Generic CRM Protocol — Complementary.
RPC rules
Every request from Generic CRM interface must receive exactly one response:
- Use the same
requestId. - On success, include
payload. - On failure, include
error(string).
Unknown methods should still respond with error, for example "unknown_method:…".
Feature checklist
| Feature | Flag / trigger | Host CRM integration implements |
|---|---|---|
| Click-to-dial | CRM Configuration | voice:bind → subscribe dial-out → voice:dialout |
| Click-to-consult | CRM Configuration | Same as dial — Generic CRM interface routes to consult when an interaction is active |
| Screen pop | Workflow attaches interaction data | Handle screenPop:interaction → interpret payload → open CRM record(s) |
| CRM directory | Infinity Element requests directory | fetchDirectory RPC → fetch list from CRM API → response |
| Omnichannel S | CRM Configuration — Synchronized | availability:bind → subscribe to status changes → availability:changed; applyUnifiedStatusByName RPC → write status → response |
| Omnichannel C | CRM Configuration — Complementary | Same Host CRM integration surface as S; complementary logic runs in Generic CRM interface |
Skip handlers for message types from Generic CRM interface your integration never receives. If click-to-dial is off in Avaya Infinity Configuration, Generic CRM interface does not post voice:bind.
Resources
Host CRM Integration | CRM Features | Generic CRM Protocol | Resources
Updated 8 days ago
