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/)

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:

  1. 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).
  2. Host CRM integration wires the CRM SDK — subscribe to an event, poll an API, or fetch data on demand.
  3. 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.

FeatureGeneric CRM interface → Host CRM integration (activate)Host CRM integration must wire in CRMHost CRM integration → Generic CRM interface (result)
Click-to-dial / consultvoice:bindSubscribe to dial-out (CTI, OpenCTI, UI click handler, etc.)voice:dialout, optionally voice:error
CRM directoryrequest + fetchDirectoryFetch contact list from host CRM APIresponse with { name, phone }[]
Omnichannelavailability:bindSubscribe to agent/presence status changesavailability:changed when status changes
Omnichannel (Infinity → Host CRM integration)request + applyUnifiedStatusByNameApply the requested status via host CRM APIresponse with { applied, echoStatusId?, … }
Screen popscreenPop:interactionOpen 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

DirectionTypeWhenHost CRM integration action
Host CRM integration → Generic CRM interfacedetachPage unload / teardownStop integration
Generic CRM interface → Host CRM integrationdetachGeneric CRM interface teardownClean up listeners
Generic CRM interface → Host CRM integrationvoice:bind / voice:unbindClick-to-dial / consult enabledSubscribe / unsubscribe to host CRM dial-out events
Host CRM integration → Generic CRM interfacevoice:dialoutUser clicks a phone number or contactStart dial or consult
Host CRM integration → Generic CRM interfacevoice:errorDial-out failedReport error (optional)
Generic CRM interface → Host CRM integrationscreenPop:interactionEvery interaction update from Agent UIOpen matching CRM record(s) when payload contains your screen-pop data
Generic CRM interface → Host CRM integrationrequestDirectory or omnichannel RPCHandle method, post response
Host CRM integration → Generic CRM interfaceresponseReply to every requestSame requestId, payload or error
Generic CRM interface → Host CRM integrationavailability:bind / availability:unbindOmnichannel enabledStart / stop reporting CRM availability
Host CRM integration → Generic CRM interfaceavailability:changedCRM availability changesPush 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

{
  "channel": "infinity-crm-bridge",
  "type": "detach"
}

Host CRM integration receives — detach

The 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

  1. On voice:bind, register your CRM dial-out listener (one subscription per bind).
  2. When the user clicks a phone number or contact, post voice:dialout (see below).
  3. On dial-out failure, optionally post voice:error.
  4. On voice:unbind or detach, remove the listener.

You send — voice:dialout

When 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)

{
  "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

  1. Handle screenPop:interaction in your message bridge.

  2. 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.

  3. 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.).

  4. 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 routeTo with a short delay between tabs).

  5. 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

{
  "channel": "infinity-crm-bridge",
  "type": "request",
  "requestId": "1",
  "method": "fetchDirectory"
}

Your implementation

  1. Handle request with method: "fetchDirectory" in your message bridge.
  2. Call your host CRM API to retrieve contacts (implement in *-directory.fetch.ts).
  3. Map each contact to { name, phone } and post a response with the same requestId.
  4. On failure, post response with error instead of payload.

The Generic CRM interface forwards the list to the Infinity Element — you do not post directory data proactively.

You send — 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:

DirectionTriggerHost CRM integration workHost CRM integration posts
Host CRM integration → Infinityavailability:bindSubscribe to agent/presence status changesavailability:changed when status changes
Infinity → Host CRM integrationrequest + applyUnifiedStatusByNameWrite the requested status via host CRM APIresponse 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

  1. On availability:bind, subscribe to agent status changes in your host CRM.
  2. When status changes, post availability:changed with { id?, name?, reason? } mapped from your CRM presence model.
  3. On availability:unbind or detach, remove the subscription.
  4. Implement read helpers in *-agent-availability.fetch.ts (see _template/template-agent-availability.fetch.ts).

You send — availability:changed

When 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

  1. Handle request with method: "applyUnifiedStatusByName" in your message bridge.
  2. Resolve the display name to your CRM's status id (if needed) and PUT/PATCH the agent status via your host CRM API.
  3. Post response with the same requestId — include echoStatusId when the next availability:changed you push will echo that id (helps Generic CRM interface avoid sync loops).
  4. Implement write helpers in *-agent-availability.fetch.ts.

You send — response

{
  "channel": "infinity-crm-bridge",
  "type": "response",
  "requestId": "3",
  "payload": {
    "applied": true,
    "echoStatusId": 2,
    "resolvedName": "Busy"
  }
}
FieldPurpose
appliedtrue if the host CRM applied the requested status
echoStatusIdOptional — set when the next availability:changed will echo this id (helps avoid sync loops)
resolvedNameOptional — actual status name applied in the Host CRM
failureReasonOptional — 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:changed and return it as echoStatusId on apply when the next push will echo that id.
  • Always respond to RPCs — Every applyUnifiedStatusByName request must get one response with the same requestId. A missing or mismatched response can stall omnichannel updates for the session.
  • Set applied from the CRM outcome — Return applied: false (and optional failureReason) when the CRM API failed or the requested name does not exist. Return applied: true only when the CRM applied the requested status — not when a different status was written.
  • resolvedName is optional metadata — The Generic CRM interface does not use it for sync; ongoing state follows availability: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

FeatureFlag / triggerHost CRM integration implements
Click-to-dialCRM Configurationvoice:bindsubscribe dial-outvoice:dialout
Click-to-consultCRM ConfigurationSame as dial — Generic CRM interface routes to consult when an interaction is active
Screen popWorkflow attaches interaction dataHandle screenPop:interaction → interpret payload → open CRM record(s)
CRM directoryInfinity Element requests directoryfetchDirectory RPC → fetch list from CRM API → response
Omnichannel SCRM Configuration — Synchronizedavailability:bindsubscribe to status changesavailability:changed; applyUnifiedStatusByName RPC → write status → response
Omnichannel CCRM Configuration — ComplementarySame 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


Did this page help you?