CRM Integration Guide

This document defines the REST interface a CRM Integration Service must expose. The service is a proxy: it sits between the desktop client and your CRM, and it is written and hosted by you. We do not see inside it. The platform only needs the endpoints below to answer in the shapes described, and everything behind them — how you authenticate to your own CRM, how you cache, how you queue — is yours to decide.

A word on the data. Your service will handle call recordings and customer records. That data is your responsibility once it reaches you, and the design of this interface deliberately does not constrain how you store or protect it.

Conventions

MarkerMeaning
OPTIONALThe endpoint may be left unimplemented. The client copes with its absence.
ASYNCProcessing is expected to run asynchronously. Return a status object immediately and let the caller poll it, rather than holding the connection open.
AuthorizationEvery endpoint except GET /token and GET /crm expects the token in an X-CrmIService-Token request header. How you issue and check that token is your decision; the platform only carries it.
TypesAll request and response bodies are JSON. A field marked can be empty may be returned as null or as an empty string.

Authentication

Issue a token, then expect it back on every later request in the X-CrmIService-Token header. Whether that token is a session key, a JWT or a random string is up to you.

Token object

FieldTypeCan be emptyDescription
idstringnoThe token supplied on subsequent requests.

Example

{
  "id": "1f3870be274f6c49b3e31a0c6728957f"
}

GET /token

Authenticate the user against the CRM and return a token for subsequent requests.

Request

$ curl -X GET https://{crmiservice-host}/token \
    -u john.goodley@woodwork.com:password8RKOtgTg3biYi5kag5UHx3Mrv

Response

StatusMeaning
200 OKSuccess. Returns a Token object.
401 UnauthorizedCRM authentication failed.
{
  "id": "1480fd6bf83a1e65c470b8fd37ee43b5"
}

Reference implementation

handler [ GET /token ]
    if CRM authentication fails
        return 401
    return <JSON<Token>> generated token, 200

Search

These endpoints answer the question the desktop client asks on every inbound call: who is this. Fields map onto whatever your CRM calls its records — Lead, Contact, Account.

Customer object

FieldTypeCan be emptyDescription
idstringnoRecord ID in the CRM.
type"Contact" | "Lead" | "Account" | "UNKNOWN"noWhich kind of CRM record this is.
webpagestringnoURL that opens this record in the CRM.
namestringyesFirst and last name.
emailstringyesEmail address.
companystringyesCompany name.
mobilephonestringyesMobile number.
workphonestringyesWork number. Note: the upstream specification’s attribute table misspells this wokrphone, while every worked example in the same document uses workphone. Implement workphone.
homephonestringyesHome number.
faxstringyesFax number.

Example

{
  "id": "0032000001DrFDWAA3",
  "type": "Contact",
  "webpage": "https://emea.salesforce.com/0032000001DrFDWAA3",
  "name": "Andy Young",
  "email": "andy.young@mail.com",
  "company": "Salesforce Inc",
  "mobilephone": "(785) 265-5350",
  "workphone": "(785) 241-6200",
  "homephone": null,
  "fax": null
}

GET /customers

Return every callable record in the CRM — accounts, contacts and leads.

Request

$ curl -X GET https://{crmiservice-host}/customers \
    -H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5"

Response

StatusMeaning
200 OKSuccess. Returns an array of Customer objects.
401 UnauthorizedInvalid or missing token.

Reference implementation

handler [ GET /customers ]
    if request is unauthorized
        return 401
    if CRM lookup errors
        return the CRM status code
    return <JSON<List<Customer>>> customers, 200

GET /customers/search

Return every record whose number matches. Partial matches are expected — the client passes the digits it has.

Request

$ curl -X GET "https://{crmiservice-host}/customers/search?phonenumber=241-6200" \
    -H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5"

Response

StatusMeaning
200 OKSuccess. Returns an array of Customer objects.
400 Bad RequestMissing or invalid query parameter.
401 UnauthorizedInvalid or missing token.

Reference implementation

handler [ GET /customers/search ]
    if request is unauthorized
        return 401
    if 'phonenumber' is not set
        return 400
    if 'phonenumber' is empty
        fall through to handler [ GET /customers ]
    if CRM lookup errors
        return the CRM status code
    return <JSON<List<Customer>>> customers, 200

Users

A user is the agent on your side of the call — the person using the desktop client, matched to their identity in the CRM.

User object

FieldTypeCan be emptyDescription
idstringnoUser ID in the CRM.
usernamestringnoUsername on the CRM platform.
agentnumberstringyesAgent number associated with the user.
namestringyesFirst and last name.
emailstringyesEmail address.
mobilephonestringyesMobile number.
workphonestringyesWork number.
faxstringyesFax number.

Example

{
  "id": "00520000003nZ4QAAU",
  "username": "maxijazz@faithless.com",
  "agentnumber": null,
  "name": "Maxwell Fraser",
  "email": "maxijazz@faithless.com",
  "mobilephone": null,
  "workphone": null,
  "fax": null
}

GET /users/searchOPTIONAL

Return the single CRM user matching the username exactly. No pattern matching, and never more than one result.

Request

$ curl -X GET "https://{crmiservice-host}/users/search?username=agent007@secretagency.com" \
    -H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5"

Response

StatusMeaning
200 OKSuccess. Returns a User object.
400 Bad RequestMissing or invalid query parameter.
401 UnauthorizedInvalid or missing token.

Reference implementation

handler [ GET /users/search ]
    if request is unauthorized
        return 401
    if 'username' is not set or empty
        return 400
    if CRM lookup errors
        return the CRM status code
    return <JSON<User>> user, 200
Correction to the upstream specification. The source document shows this endpoint returning a list of Customer objects, and labels its reference implementation GET /customers/search. Both are copy-and-paste errors in that document. This endpoint returns a single User.

CRM information

Identifies which CRM is behind the integration. This endpoint is not authorized — it carries nothing sensitive.

Crm object

FieldTypeCan be emptyDescription
namestringnoName of the CRM platform — Salesforce, ZOHO, HubSpot and so on.
urlstringnoURL of the CRM home page.
versionstringnoVersion of the CRM platform or of its API.

Example

{
  "name": "Salesforce",
  "url": "https://login.salesforce.com",
  "version": "14.00"
}

GET /crm

Return basic information about the integrated CRM.

Request

$ curl -X GET https://{crmiservice-host}/crm

Response

StatusMeaning
200 OKSuccess. Returns a Crm object.
404 Not FoundNo CRM information available.

Reference implementation

handler [ GET /crm ]
    if CRM lookup errors
        return the CRM status code
    return <JSON<Crm>> crm, 200

Call logs and recordings

If your CRM has no concept of call logging, still expose these endpoints. Answer with success and a status of READY; the status object itself may be empty. Silence here stalls the client.

CallLogRequest object

FieldTypeCan be emptyDescription
customeridstringyesCaller’s record ID in the CRM.
customertype"Contact" | "Lead" | "Account" | "UNKNOWN"yesWhich kind of CRM record the caller is.
subjectstringnoCall subject.
phonenumberstringyesThe other party’s number, caller or callee depending on direction.
direction"OUTBOUND" | "INBOUND"noDirection of the call.
durationintnoCall duration in seconds.
starttimeintnoUNIX timestamp of the call start.
status"ANSWERED" | "UNANSWERED" | "BUSY" | "UNAVAILABLE" | "INPROGRESS"noState of the call.
descriptionstringnoCall description.
asteriskcallid1stringnoCaller’s channel ID.
asteriskcallid2stringnoCallee’s channel ID.
recordupload1 | 0noWhether the recording should be uploaded to the CRM.
recordnamestringyesRecording file name.
recorddescstringyesRecording description.
recording_urlstringyesDirect URL to the self-care portal page for this call. After signing in, the user reaches the CDR entry for the call and all its legs, and can play or download the recording. Supported by desktop client 7.5 and above.

Example

{
  "customerid": "0032000001DrFDSAA3",
  "customertype": "Contact",
  "subject": "API call",
  "phonenumber": "202 3893-293",
  "direction": "OUTBOUND",
  "duration": 0,
  "starttime": 1684931119,
  "status": "UNANSWERED",
  "description": "Unanswered call by customer",
  "asteriskcallid1": "1407350434.61",
  "asteriskcallid2": "1407350434.62",
  "recordupload": 0,
  "recordname": "",
  "recorddesc": "",
  "recording_url": ""
}

CallLogResponse object

FieldTypeCan be emptyDescription
idstringnoCall log ID in the CRM.
useridstringyesUser ID in the CRM.
customeridstringnoCaller’s record ID in the CRM.
customertype"Contact" | "Lead" | "Account" | "UNKNOWN"noWhich kind of CRM record the caller is.
subjectstringnoCall subject.
phonenumberstringnoThe other party’s number.
direction"OUTBOUND" | "INBOUND"noDirection of the call.
durationintnoCall duration in seconds.
starttimeintnoUNIX timestamp of the call start.
status"ANSWERED" | "UNANSWERED" | "BUSY" | "UNAVAILABLE" | "INPROGRESS"noState of the call.
descriptionstringyesCall description.
asteriskcallid1stringyesCaller’s channel ID.
asteriskcallid2stringyesCallee’s channel ID.
recordnamestringyesRecording file name.
recorddescstringyesRecording description.
recording_urlstringyesDirect URL to the recording in the self-care portal.

Example

{
  "id": "23487cdc093e810a01ff0",
  "userid": "",
  "customerid": "0032000001DrFDSAA3",
  "customertype": "Contact",
  "subject": "API call",
  "phonenumber": "202 3893-293",
  "direction": "OUTBOUND",
  "duration": 0,
  "starttime": 1684931119,
  "status": "UNANSWERED",
  "description": "Unanswered call by customer",
  "asteriskcallid1": "1407350434.61",
  "asteriskcallid2": "1407350434.62",
  "recordname": "",
  "recorddesc": "",
  "recording_url": ""
}

Status object

FieldTypeCan be emptyDescription
idstringnoStatus ID for the resource.
status"PENDING" | "FAILED" | "CANCELED" | "READY"noCurrent state of processing.
timestampintnoUNIX timestamp of the move into READY.
timetoliveintnoSeconds the resource stays available after reaching READY.
resourcetype"CallLog" | "CallRecord"noType of resource being processed.
resourceidstringyesResource ID. Must not be empty once status is READY.
messagestringyesAdditional status information.

Example

{
  "id": "c285ac7b-8784-4894-923f-4769ae2e1261",
  "status": "READY",
  "timestamp": 1684499360,
  "timetolive": 86400,
  "resourcetype": "CallLog",
  "resourceid": "00T2000001uiCveEAE",
  "message": ""
}

Query parameters on POST /calllog

ParameterDefaultMeaning
async"yes"Whether to process asynchronously. "yes" or "no".
statusid""A status ID the client received from an earlier request.

Supporting both is recommended. If you cannot, behave as though the defaults were passed.

POST /calllogASYNC

Create a call log in the CRM from the posted CallLogRequest. When processing asynchronously, start the work, generate a status ID and return immediately.

Request

$ curl -X POST https://{crmiservice-host}/calllog \
    -H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5" \
    -d '{
      "customerid": "",
      "customertype": "",
      "subject": "API call",
      "phonenumber": "232 8932-225",
      "direction": "INBOUND",
      "duration": 93,
      "starttime": 1684497234,
      "status": "ANSWERED",
      "description": "Answered call by agent at support call center",
      "asteriskcallid1": "1407351358.61",
      "asteriskcallid2": "1407351358.62",
      "recordupload": 0,
      "recordname": "",
      "recorddesc": ""
    }'

Response

StatusMeaning
200 OKSuccess. Returns a Status object.
400 Bad RequestInvalid JSON body or query parameters.
401 UnauthorizedInvalid or missing token.
409 ConflictFailed to create the call log. Synchronous requests only.
{
  "id": "c285ac7b-8784-4894-923f-4769ae2e1261",
  "status": "PENDING",
  "timestamp": 1684499240,
  "timetolive": 86400,
  "resourcetype": null,
  "resourceid": null
}

Reference implementation

handler [ POST /calllog ]
    if request is unauthorized
        return 401
    if posted body is invalid
        return 400
    if 'statusid' is not set
        'statusid' = ""
    if 'async' is not set
        'async' = true
    if no stored record for 'statusid'
        store a new record keyed by a generated status ID
    if 'async'
        run the CRM write in the background against 'statusid'
        mark 'statusid' PENDING
        return <JSON<Status>> stored record, 200
    if the CRM write errors
        mark 'statusid' FAILED
        return 409
    mark 'statusid' READY
    return <JSON<Status>> stored record, 200

GET /status/{id}

Return the current processing state of a resource.

Request

$ curl -X GET https://{crmiservice-host}/status/c285ac7b-8784-4894-923f-4769ae2e1261 \
    -H "X-CrmIService-Token: 1480fd6bf83a1e65c470b8fd37ee43b5"

Response

StatusMeaning
200 OKSuccess. Returns a Status object.
401 UnauthorizedInvalid or missing token.
404 Not FoundNo such status or resource.
{
  "id": "c285ac7b-8784-4894-923f-4769ae2e1261",
  "status": "READY",
  "timestamp": 1684499266,
  "timetolive": 86400,
  "resourcetype": "CallLog",
  "resourceid": "23487cdc093e810a01ff0"
}

Reference implementation

handler [ GET /status/{id} ]
    if request is unauthorized
        return 401
    if no stored record for the ID
        return 404
    return <JSON<Status>> stored record, 200

Notes

  1. Every name, number and record ID in this document is invented for illustration. None refers to a real person.
  2. Your platform is most likely carrying live calls while you build against this interface. Load generated by testing lands on the same system your users are on.
  3. Authentication between your integration service and your CRM is entirely yours to design. The platform knows only a username, a password and the token you issue; it relies on you to make the exchange secure.

Questions on this document

Send them to support@ahoytel.com and they reach the engineers who run the platform, not a queue. If something here does not match what your service actually receives, tell us — that is a defect in this document and we would rather fix it than have you work around it.