iPlum usage logs API documentation

Introduction

iPlum API is built using REST (Representational State Transfer). API calls can be made using all popular programming languages. The API is served exclusively over HTTPS, enabling a wide range of HTTP clients to interact with the iPlum API.

Getting Started

Access to the iPlum API requires that the account has the API feature enabled.

  1. Log in to your iPlum account with your administrator credentials at https://my.iplum.com.
  2. Go to Left Menu → API & Call Backs.
  3. Toggle the API switch to enable it.

API Endpoint

The URL of each resource can be obtained by accessing the API Root endpoint:

https://api.iplum.com/

HTTP Method

All iPlum APIs are accessed over HTTPS and use POST requests for every action. POST is used to get data, create resources, and perform resource actions.

Request Headers

Every request should have the following mandatory headers.

  • X-iPlumAuth-AccessKey — The access key generated by the API.
  • X-iPlumAuth-Username — The iPlum administrator login ID.
  • X-iPlumAuth-EpochDateTime — Date-time in Epoch format.
  • X-iPlumAuth-Version — The API version for the request.
  • AuthorizationiPlumAuth <signature>
  • Content-Typeapplication/json

Authorization

To generate the iPlum API authorization header, use the HMAC-SHA256 (Hash Message Authentication Code) signing algorithm.

Steps to Generate HMAC Signature

1. Generate access keys:

  • Log into the iPlum website at https://my.iplum.com.
  • Click on the API & Call Backs link on the left menu bar.
  • Toggle the API switch if not enabled.
  • Download and save your API access and secret keys.

2. Create a request for the iPlum API:

  • Create an HTTP request object:

HttpRequestMessage request = new HttpRequestMessage();

  • Create an HTTP header with the API access key:

request.Headers.Add("X-iPlumAuth-AccessKey", "ABCD123ACCESSKEY");

  • Create an HTTP header with the iPlum administrator login ID:

request.Headers.Add("X-iPlumAuth-Username", "test@testuser.com");

  • Create an HTTP header with the version:

request.Headers.Add("X-iPlumAuth-Version", "v1");

  • Create an HTTP header with the epoch date-time:

request.Headers.Add("X-iPlumAuth-EpochDateTime", "1490752181");

3. Create a concatenated string by joining the following values with a newline (\n), in this exact order:

  • HTTP method requested (in lower case). E.g., post
  • API access key.
  • iPlum administrator login ID (in lower case).
  • Version (in lower case). E.g., v1
  • Epoch date-time.
  • The URL path of the requested resource (in lower case). E.g., /usage/getusage

Use this concatenated string as the representation:

string representation = String.Join("\n",
   httpMethod.ToLower(),
   requestMessage.Headers.GetValues("X-iPlumAuth-AccessKey").FirstOrDefault(),
   requestMessage.Headers.GetValues("X-iPlumAuth-Username").FirstOrDefault(),
   requestMessage.Headers.GetValues("X-iPlumAuth-Version").FirstOrDefault().ToLower(),
   requestMessage.Headers.GetValues("X-iPlumAuth-EpochDateTime").FirstOrDefault().ToLower(),
   uri.ToLower());

4. Generate an HMAC signature using both the secret key and the representation.

5. Base-64 encode the generated HMAC signature and add it to the Authorization header:

requestMessage.Headers.Add("Authorization", "iPlumAuth" + " " + signature);

6. The final iPlum API request header should look like the example below:

Content-Type: application/json
X-iPlumAuth-AccessKey: ABCD1234ACCESSKEY
X-iPlumAuth-Username: test@testuser.com
X-iPlumAuth-Version: v1
X-iPlumAuth-EpochDateTime: 1490752181
Authorization: iPlumAuth w5tcOEC7xZIQOhxMMGDB6U83fpFReiUYiGVCwZHgaHI=

7. Send content within the body section of the request.

Complete Signing Examples

The following helpers build the full set of authorization headers (including the Authorization signature) for a request, following the steps above.

Node.js:

const crypto = require('crypto');

function buildAuthHeaders({ accessKey, secretKey, username }, method, url) {
 const epoch = Math.floor(Date.now() / 1000).toString();
 const path = new URL(url).pathname.toLowerCase();
 const version = 'v1';
 const stringToSign = [
   method.toLowerCase(),
   accessKey,
   username.toLowerCase(),
   version,
   epoch,
   path,
 ].join('\n');
 const signature = crypto
   .createHmac('sha256', secretKey)
   .update(stringToSign, 'utf8')
   .digest('base64');
 return {
   'X-iPlumAuth-AccessKey': accessKey,
   'X-iPlumAuth-Username': username,
   'X-iPlumAuth-Version': version,
   'X-iPlumAuth-EpochDateTime': epoch,
   Authorization: `iPlumAuth ${signature}`,
 };
}

Python:

import time, hmac, hashlib, base64
from urllib.parse import urlparse

def build_auth_headers(access_key, secret_key, username, method, url):
   epoch = str(int(time.time()))
   path = urlparse(url).path.lower()
   version = "v1"
   string_to_sign = "\n".join(
       [method.lower(), access_key, username.lower(), version, epoch, path]
   )
   signature = base64.b64encode(
       hmac.new(secret_key.encode("utf-8"),
                string_to_sign.encode("utf-8"),
                hashlib.sha256).digest()
   ).decode("utf-8")
   return {
       "X-iPlumAuth-AccessKey": access_key,
       "X-iPlumAuth-Username": username,
       "X-iPlumAuth-Version": version,
       "X-iPlumAuth-EpochDateTime": epoch,
       "Authorization": f"iPlumAuth {signature}",
   }

C#:

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

static Dictionary<string, string> BuildAuthHeaders(
   string accessKey, string secretKey, string username, string method, Uri url)
{
   var epoch = ((long)(DateTime.UtcNow -
       new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds).ToString();
   var version = "v1";
   var path = url.AbsolutePath.ToLowerInvariant();
   var stringToSign = string.Join("\n",
       method.ToLowerInvariant(), accessKey, username.ToLowerInvariant(), version, epoch, path);
   string signature;
   using (var h = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey)))
       signature = Convert.ToBase64String(h.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
   return new Dictionary<string, string>
   {
       ["X-iPlumAuth-AccessKey"] = accessKey,
       ["X-iPlumAuth-Username"] = username,
       ["X-iPlumAuth-Version"] = version,
       ["X-iPlumAuth-EpochDateTime"] = epoch,
       ["Authorization"] = $"iPlumAuth {signature}",
   };
}

Bash (with curl):

# Requires: bash, openssl, curl. Set these first:
#   export IPLUM_BASE_URL="https://api.iplum.com"
#   export IPLUM_ACCESS_KEY="your-access-key"
#   export IPLUM_SECRET_KEY="your-secret-key"
#   export IPLUM_USERNAME="admin@example.com"
#
# Usage: iplum_curl <METHOD> <PATH> [JSON_BODY]
iplum_curl() {
 local method="$1" path="$2" body="${3:-}"
 local epoch; epoch=$(date +%s)
 local m_lc p_lc u_lc
 m_lc=$(printf '%s' "$method"          | tr '[:upper:]' '[:lower:]')
 p_lc=$(printf '%s' "$path"            | tr '[:upper:]' '[:lower:]')
 u_lc=$(printf '%s' "$IPLUM_USERNAME"  | tr '[:upper:]' '[:lower:]')
 local sts; sts=$(printf '%s\n%s\n%s\n%s\n%s\n%s' \
   "$m_lc" "$IPLUM_ACCESS_KEY" "$u_lc" "v1" "$epoch" "$p_lc")
 local sig; sig=$(printf '%s' "$sts" | openssl dgst -sha256 -hmac "$IPLUM_SECRET_KEY" -binary | base64)
 curl -sS -X "$method" "$IPLUM_BASE_URL$path" \
   -H "X-iPlumAuth-AccessKey: $IPLUM_ACCESS_KEY" \
   -H "X-iPlumAuth-Username: $IPLUM_USERNAME" \
   -H "X-iPlumAuth-Version: v1" \
   -H "X-iPlumAuth-EpochDateTime: $epoch" \
   -H "Authorization: iPlumAuth $sig" \
   ${body:+-H "Content-Type: application/json" -d "$body"}
}


Java:

import java.net.URI;

import java.nio.charset.StandardCharsets;

import java.util.Base64;

import java.util.LinkedHashMap;

import java.util.Map;

import javax.crypto.Mac;

import javax.crypto.spec.SecretKeySpec;

public static Map<String, String> buildAuthHeaders(

       String accessKey, String secretKey, String username, String method, String url) throws Exception {

   String epoch = Long.toString(System.currentTimeMillis() / 1000L);

   String path = URI.create(url).getPath().toLowerCase();

   String version = "v1";

   String stringToSign = String.join("\n",

       method.toLowerCase(), accessKey, username.toLowerCase(), version, epoch, path);

   Mac mac = Mac.getInstance("HmacSHA256");

   mac.init(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));

   String signature = Base64.getEncoder().encodeToString(

       mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)));

   Map<String, String> headers = new LinkedHashMap<>();

   headers.put("X-iPlumAuth-AccessKey", accessKey);

   headers.put("X-iPlumAuth-Username", username);

   headers.put("X-iPlumAuth-Version", version);

   headers.put("X-iPlumAuth-EpochDateTime", epoch);

   headers.put("Authorization", "iPlumAuth " + signature);

   return headers;

}

PHP:

<?php

function build_auth_headers($accessKey, $secretKey, $username, $method, $url) {

   $epoch = (string) time();

   $path = strtolower(parse_url($url, PHP_URL_PATH));

   $version = 'v1';

   $stringToSign = implode("\n", [

       strtolower($method), $accessKey, strtolower($username), $version, $epoch, $path,

   ]);

   $signature = base64_encode(hash_hmac('sha256', $stringToSign, $secretKey, true));

   return [

       'X-iPlumAuth-AccessKey'     => $accessKey,

       'X-iPlumAuth-Username'      => $username,

       'X-iPlumAuth-Version'       => $version,

       'X-iPlumAuth-EpochDateTime' => $epoch,

       'Authorization'             => "iPlumAuth $signature",

   ];

}

Response

All iPlum API requests and responses use JSON format.

Response Codes

Responses to requests made to the iPlum API are categorized under the following codes:

  • 200 — Success. The request was successfully completed. Requested data is provided in the content section.
  • 400 — Bad Request. The request cannot be fulfilled, usually because of a malformed or missing parameter.
  • 401 — Unauthorized. Request was rejected because of invalid authentication credentials.
  • 403 — Forbidden. The requesting user does not have enough permission to access the resource.
  • 404 — URL Not Found. The URL sent is wrong. It's possible that the requested resource has been moved to another URL.
  • 405 — Method Not Allowed. The requested resource does not support the HTTP method used. For example, requesting the List of all customers API with PUT as the HTTP method.
  • 406 — Not Acceptable. The requested response type is not supported by the client.
  • 429 — Too Many Requests. Too many requests within a certain time frame.
  • 500 — Server Error. iPlum Developer APIs' server encountered an error which prevents it from fulfilling the request. Although this rarely happens, we recommend you contact us if you receive this error.

Error Response

In case of errors (HTTP status code 400 / 500), the iPlum API returns an error object with the following format. Each endpoint lists its own error codes in an Error Codes section.

  • errorCode — Error code. Data Type: String
  • errorDescription — Error description in the language specified in the request. Data Type: String

Usage (Call, Text, Fax Logs)

A user can use iPlum for calls, text, and/or fax. This usage information (history) can be requested using the iPlum API.

Get Usage Records

This API call is used to fetch call, text, and/or fax usage logs.

URL: POST /usage/getusage

Request Model

  • Lang — ISO 639-1 language code. Data Type: String. Default: en
  • Usage — List of all usage types. Multiple values can be provided using a comma-separated list. Data Type: String. Default: Empty. Accepted values: iPlumtext, externalcall, externaltext, fax — iPlumtext: iPlum secure text, externalcall: External call, externaltext: External text, fax: Fax
  • StartDate — Start date filter for usage records. Data Type: String. Default: Empty. Accepted format: Date-time in UTC.
  • EndDate — End date filter for usage records. Note: The end date should not be greater than 5 days from the start date. Data Type: String. Default: Empty. Accepted format: Date-time in UTC.

Request Header Example

Content-Type: application/json
X-iPlumAuth-AccessKey: ABCD1234ACCESSKEY
X-iPlumAuth-Username: test@testuser.com
X-iPlumAuth-Version: v1
X-iPlumAuth-EpochDateTime: 1696387075
Authorization: iPlumAuth Hn6YFjVUpQCLgH8AWss8Nyp5M6eQiOKnaMZkExTJL/4=

Request Body Example

{"Lang":"en", "Usage":"externalcall,externaltext", "StartDate":"2023-03-20 02:23:06.000", "EndDate":"2023-03-24 02:23:06.000"}

Response Model

The response is a list of usage records.

  • source — Source of usage record. Data Type: String. Value: iPlum
  • clientReferenceId — Custom value set on the account. Data Type: String
  • recordId — Unique usage record ID. Data Type: String
  • usage — Usage category. Data Type: String. Possible values: call, text, fax
  • type — Usage type. Data Type: String. Possible values: iplum, external
  • from — From number. Data Type: String
  • to — To number. Data Type: String
  • startTimeUTC — Start date and time of usage. Data Type: String. Format: Date-time in UTC.
  • endTimeUTC — End date and time of usage. Data Type: String. Format: Date-time in UTC.
  • duration — Duration of the call in minutes. For text, this value will be 1. Data Type: String
  • direction — Direction of usage. Data Type: String. Possible values: in, out
  • iPlumCredits — iPlum credits used in this usage. Data Type: Integer
  • userId — iPlum user to which the usage belongs. Data Type: String

Response Example

[{"source":"iPlum", "clientReferenceId":"1234", "usage":"call", "recordId":"0UmFAs73cwKbyqUtmz7ltLI.nGlAD.0Dgjdhpm", "from":"+14081231234", "to":"test@iplum.com", "startTimeUTC":"2023-03-24T00:00:00Z", "endTimeUTC":"2023-03-24T00:00:00Z", "duration":"2", "direction":"in", "type":"external", "iPlumCredits":2, "userId":"test@iplum.com"}]

Error Codes

(see Error Response above for the object format)

  • ERR_INVALID_DATE — The date range specified in the request is not valid. The end date should not be later than five days from the start date.
  • ERR_INVALID_REQUEST — Request has some invalid parameters.

Get Usage Content

This API call is used to fetch the content of sent/received text or fax. It requires the account to have a valid security & backup plan.

URL: POST /usage/getcontent

Request Model

  • Lang — ISO 639-1 language code. Data Type: String. Default: en
  • RecordID — Usage record ID. Data Type: String

Request Header Example

Content-Type: application/json
X-iPlumAuth-AccessKey: ABCD1234ACCESSKEY
X-iPlumAuth-Username: test@testuser.com
X-iPlumAuth-Version: v1
X-iPlumAuth-EpochDateTime: 1696387155
Authorization: iPlumAuth FvWXGLoxw0zy9tu381IyebstzBVCw8YJtVm+VGmtQIo=

Request Body Example

{"Lang":"en", "RecordID":"0UmFAs73cwKbyqUtmz7ltLI.nGlAD.0Dgjdhpm"}

Response Model

The response is a string representing text/fax content. It could be text, or a URL for a photo/audio/video attachment or fax file.

Text example: Hello

Media URL example: http://media.iplum.com/af542549-527a-47c8-a183-b853455501c0.jpg

Error Codes

  • ERR_INVALID_REQUEST — Request has some invalid parameters.

Call Recordings

Users can record incoming and/or outgoing calls. It requires the user to have a valid call recording plan.

Get Recording Records

This API call is used to fetch call recording logs.

URL: POST /callrecordings/getrecordings

Request Model

  • Lang — ISO 639-1 language code. Data Type: String. Default: en
  • StartDate — Start date filter for usage records. Data Type: String. Default: Empty. Accepted format: Date-time in UTC.
  • EndDate — End date filter for usage records. Note: The end date should not be greater than 5 days from the start date. Data Type: String. Default: Empty. Accepted format: Date-time in UTC.

Request Header Example

Content-Type: application/json
X-iPlumAuth-AccessKey: ABCD1234ACCESSKEY
X-iPlumAuth-Username: test@testuser.com
X-iPlumAuth-Version: v1
X-iPlumAuth-EpochDateTime: 1696387193
Authorization: iPlumAuth w5VKuiMqd38g53ksT8xBuoShgu0Xj1NfXx8WHoDCG3A=

Request Body Example

{"Lang":"en", "StartDate":"2023-03-20 02:23:06.000", "EndDate":"2023-03-24 02:23:06.000"}

Response Model

The response is a list of call recording records.

  • source — Source of call recording record. Data Type: String. Value: iPlum
  • clientReferenceId — Custom value set on the account. Data Type: String
  • recordId — Unique usage record ID. Data Type: String
  • phonenumber — Phone number of the called/calling party. Data Type: String
  • dateTimeUTC — Date and time of recording. Data Type: String. Format: Date-time in UTC.
  • duration — Duration of the call recording in minutes. Data Type: String
  • length — Length of the recording file in KB or MB. Data Type: String
  • direction — Direction of call. Data Type: String. Possible values: in, out
  • userId — iPlum user to which the usage belongs. Data Type: String

Response Example

[{"source":"iPlum", "clientReferenceId":"1234", "recordId":"ae0b4e3e-76c0-46d2-8bab-18c8764081dbgjdi2j", "phonenumber":"+14081231234", "dateTimeUTC":"2023-03-22T00:24:00Z", "duration":"3", "direction":"in", "length":"434.2 KB", "userId":"test@iplum.com"}]

Error Codes

  • ERR_INVALID_DATE — The date range specified in the request is not valid. The end date should not be greater than 5 days from the start date.
  • ERR_INVALID_REQUEST — Request has some invalid parameters.

Get Recording File

This API call is used to fetch the recording file for the given record.

URL: POST /callrecordings/getrecordingfile

Request Model

  • Lang — ISO 639-1 language code. Data Type: String. Default: en
  • RecordID — Call recording record ID. Data Type: String

Request Header Example

Content-Type: application/json
X-iPlumAuth-AccessKey: ABCD1234ACCESSKEY
X-iPlumAuth-Username: test@testuser.com
X-iPlumAuth-Version: v1
X-iPlumAuth-EpochDateTime: 1696387235
Authorization: iPlumAuth XMFT4svyRWjm/wwK+FfQjEo24jJpMpt99Uv7fs2PN10=

Request Body Example

{"Lang":"en", "RecordID":"ae0b4e3e-76c0-46d2-8bab-18c8764081dbgjdi2j"}

Response Model

The response is a string URL for the recording file.

Example: http://media.iplum.com/ae0b4e3e-76c0-46d2-8bab-18c8764081db.mp3

Error Codes

  • ERR_INVALID_REQUEST — Request has some invalid parameters.

Callbacks

Callbacks are real-time, event-driven notifications.

Callback Signature Verification

iPlum signs outbound callback (webhook) requests so your endpoint can verify that a request genuinely originated from iPlum, that the payload was not tampered with in transit, and that it is not a replay of an older delivery. This applies to all callback types – usage callbacks (call, text, and/or fax) and call recording callbacks.

Headers

Every signed callback POST includes the following headers:

  • X-iPlum-Access-Key — Your iPlum API Access Key. Identifies which credential was used to sign the request, so you know which Secret Key to use for verification.
  • X-iPlum-Timestamp — Unix epoch time, in seconds, at the moment iPlum signed the request.
  • X-iPlum-Signaturesha256= followed by the Base64-encoded HMAC-SHA256 signature.

The request body is sent in JSON format (Content-Type: application/json). If HTTP Basic authentication is also configured for your callback URL, the Authorization: Basic header is included in addition to the headers above.

Signature

iPlum builds a canonical string representation of the request and signs it using HMAC-SHA256 with your iPlum API Secret Key, then Base64-encodes the result. The representation is three lines joined by a newline (\n):

post
<X-iPlum-Timestamp value>
<raw request body>signature = Base64( HMAC_SHA256( SecretKey, "post" + "\n" + timestamp + "\n" + rawBody ) )

  • Line 1 is the HTTP method in lower case: post
  • Line 2 is exactly the X-iPlum-Timestamp header value.
  • Line 3 is the raw request body exactly as received – do not re-serialize, reorder keys, or re-encode the JSON before verifying.

This uses the same HMAC-SHA256 signing method and Secret Key as iPlum's inbound API request signing (see Steps to Generate HMAC Signature above); the outbound representation additionally covers the request body, so payload integrity is protected as well.

Verification Steps

  1. Read X-iPlum-Timestamp and X-iPlum-Signature from the incoming request.
  2. Reject the request if the timestamp is more than 5 minutes (300 seconds) away from the current time – this is the replay-attack check.
  3. Rebuild the representation: "post" + newline + timestamp + newline + the raw request body.
  4. Compute Base64(HMAC-SHA256(YourSecretKey, representation)).
  5. Compare the result to the value after sha256= in X-iPlum-Signature using a constant-time comparison.
  6. Accept the callback only if the signatures match.

Use your iPlum API Secret Key – the same key you use to sign requests you send to the iPlum API. The X-iPlum-Access-Key header tells you which key applies.

Reference Implementations

Node.js (Express):

const crypto = require('crypto');

// Ensure you have the RAW body. With Express:
// app.use('/iplum/callback', express.raw({ type: '*/*' }));
function verifyIplumCallback(req, secretKey) {
 const timestamp = req.get('X-iPlum-Timestamp');
 const header = req.get('X-iPlum-Signature') || '';
 const provided = header.startsWith('sha256=') ? header.slice('sha256='.length) : header;

 // Replay protection (5 minutes)
 const nowSec = Math.floor(Date.now() / 1000);
 if (!timestamp || Math.abs(nowSec - Number(timestamp)) > 300) return false;

 const rawBody = req.body.toString('utf8'); // Buffer -> exact bytes as received
 const representation = ['post', timestamp, rawBody].join('\n');
 const expected = crypto.createHmac('sha256', secretKey).update(representation, 'utf8').digest('base64');

 const a = Buffer.from(expected);
 const b = Buffer.from(provided);
 return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Python (Flask):

import base64, hashlib, hmac, time

def verify_iplum_callback(headers, raw_body: bytes, secret_key: str) -> bool:
   timestamp = headers.get("X-iPlum-Timestamp")
   header = headers.get("X-iPlum-Signature", "")
   provided = header[len("sha256="):] if header.startswith("sha256=") else header

   # Replay protection (5 minutes)
   if not timestamp or abs(int(time.time()) - int(timestamp)) > 300:
       return False

   representation = "\n".join(["post", timestamp, raw_body.decode("utf-8")]).encode("utf-8")
   digest = hmac.new(secret_key.encode("utf-8"), representation, hashlib.sha256).digest()
   expected = base64.b64encode(digest).decode("ascii")
   return hmac.compare_digest(expected, provided)

C#:

using System;

using System.Security.Cryptography;

using System.Text;

// Pass the RAW request body string exactly as received (do not re-serialize).

static bool VerifyIplumCallback(string timestamp, string signatureHeader, string rawBody, string secretKey)

{

   signatureHeader ??= "";

   var provided = signatureHeader.StartsWith("sha256=")

       ? signatureHeader.Substring("sha256=".Length)

       : signatureHeader;

   // Replay protection (5 minutes)

   var nowSec = (long)(DateTime.UtcNow -

       new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;

   if (!long.TryParse(timestamp, out var ts) || Math.Abs(nowSec - ts) > 300)

       return false;

   var representation = string.Join("\n", "post", timestamp, rawBody);

   string expected;

   using (var h = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey)))

       expected = Convert.ToBase64String(h.ComputeHash(Encoding.UTF8.GetBytes(representation)));

   // Constant-time comparison (returns false if lengths differ)

   return CryptographicOperations.FixedTimeEquals(

       Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(provided));

}

Bash:

# Requires: bash, openssl. Read the raw body from a file to preserve exact bytes.

# Usage: verify_iplum_callback <timestamp> <signature-header> <raw-body-file> <secret-key>

verify_iplum_callback() {

 local timestamp="$1" sig_header="$2" body_file="$3" secret="$4"

 local provided="${sig_header#sha256=}"

 # Replay protection (5 minutes)

 [ -z "$timestamp" ] && return 1

 local now diff; now=$(date +%s); diff=$(( now - timestamp )); diff=${diff#-}

 [ "$diff" -gt 300 ] && return 1

 # representation = "post\n<timestamp>\n<raw body>"

 local expected

 expected=$( { printf 'post\n%s\n' "$timestamp"; cat "$body_file"; } \

   | openssl dgst -sha256 -hmac "$secret" -binary | base64 )

 # Constant-time comparison via double-HMAC blinding

 [ "${#expected}" -ne "${#provided}" ] && return 1

 local key he hp; key=$(openssl rand -hex 16)

 he=$(printf '%s' "$expected" | openssl dgst -sha256 -hmac "$key" | awk '{print $NF}')

 hp=$(printf '%s' "$provided" | openssl dgst -sha256 -hmac "$key" | awk '{print $NF}')

 [ "$he" = "$hp" ]

}

Java:

import java.nio.charset.StandardCharsets;

import java.security.MessageDigest;

import java.util.Base64;

import javax.crypto.Mac;

import javax.crypto.spec.SecretKeySpec;

// Pass the RAW request body exactly as received (do not re-serialize).

public static boolean verifyIplumCallback(

       String timestamp, String signatureHeader, String rawBody, String secretKey) throws Exception {

   if (signatureHeader == null) signatureHeader = "";

   String provided = signatureHeader.startsWith("sha256=")

       ? signatureHeader.substring("sha256=".length()) : signatureHeader;

   // Replay protection (5 minutes)

   if (timestamp == null) return false;

   long now = System.currentTimeMillis() / 1000L;

   if (Math.abs(now - Long.parseLong(timestamp)) > 300) return false;

   String representation = String.join("\n", "post", timestamp, rawBody);

   Mac mac = Mac.getInstance("HmacSHA256");

   mac.init(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));

   String expected = Base64.getEncoder().encodeToString(

       mac.doFinal(representation.getBytes(StandardCharsets.UTF_8)));

   // Constant-time comparison (returns false if lengths differ)

   return MessageDigest.isEqual(

       expected.getBytes(StandardCharsets.UTF_8), provided.getBytes(StandardCharsets.UTF_8));

}

PHP:

<?php

// Pass the RAW request body exactly as received, e.g. file_get_contents('php://input').

function verify_iplum_callback($timestamp, $signatureHeader, $rawBody, $secretKey) {

   $provided = (strpos((string) $signatureHeader, 'sha256=') === 0)

       ? substr($signatureHeader, strlen('sha256=')) : (string) $signatureHeader;

   // Replay protection (5 minutes)

   if ($timestamp === null || $timestamp === '' || abs(time() - (int) $timestamp) > 300) {

       return false;

   }

   $representation = implode("\n", ['post', $timestamp, $rawBody]);

   $expected = base64_encode(hash_hmac('sha256', $representation, $secretKey, true));

   // Constant-time comparison

   return hash_equals($expected, $provided);

}

Notes

  • Always serve your callback endpoint over HTTPS – iPlum rejects non-HTTPS callback URLs.
  • Verify over the raw body bytes. Many frameworks parse JSON before your handler runs, so capture the raw body before verifying.
  • Treat the Secret Key as a credential: store it securely and rotate it if it is ever exposed.

Usage Callbacks

Clients can subscribe to notifications for call, text, and/or fax events. A notification (i.e., a response in the form of a usage record) is sent to the client.

Notifications are sent to the URI provided by the client during the API account setup.

Response

  • source — Source of usage record. Data Type: String. Value: iPlum
  • clientReferenceId — Custom value set on the account. Data Type: String
  • recordId — Unique usage record ID. Data Type: String
  • usage — Usage category. Data Type: String. Possible values: call, text, fax
  • type — Usage type. Data Type: String. Possible values: iplum, external
  • from — From number. Data Type: String
  • to — To number. Data Type: String
  • startTimeUTC — Start date and time of usage. Data Type: String. Format: Date-time in UTC.
  • endTimeUTC — End date and time of usage. Data Type: String. Format: Date-time in UTC.
  • duration — Duration of the call in minutes. For text, this value will be 1. Data Type: String
  • direction — Direction of usage. Data Type: String. Possible values: in, out
  • content — Text content (if enabled in callback settings and the user has a valid backup plan). Data Type: String
  • iPlumCredits — iPlum credits used in this usage. Data Type: Integer
  • userId — iPlum user to which the usage belongs. Data Type: String

Example

{"source": "iPlum", "clientReferenceId": "1234", "usage": "text", "recordId": "0UmFAs73cwKbyqUtmz7ltLI.nGlAD.0Dgjdhpm", "from": "+14081231234", "to": "test@iplum.com", "startTimeUTC": "2023-03-24T00:00:00Z", "endTimeUTC": "2023-03-24T00:00:00Z", "duration": "1", "direction": "in", "type": "external", "content": "Hello", "iPlumCredits": 1, "userId": "test@iplum.com"}

Call Recording Callbacks

Clients can subscribe to a notification when a new call recording is available. A notification (i.e., a response in the form of a call recording record) is sent to the client.

Notifications are sent to the URI provided by the client during the API account setup.

Response

  • source — Source of call recording record. Data Type: String. Value: iPlum
  • clientReferenceId — Custom value set on the account. Data Type: String
  • recordId — Unique usage record ID. Data Type: String
  • phonenumber — Phone number of the called/calling party. Data Type: String
  • dateTimeUTC — Date and time of recording. Data Type: String. Format: Date-time in UTC.
  • duration — Duration of the call recording in minutes. Data Type: String
  • length — Length of the recording file in KB or MB. Data Type: String
  • direction — Direction of call. Data Type: String. Possible values: in, out
  • content — Recording file URL (if enabled in callback settings and the user has a valid call recording plan). Data Type: String
  • userId — iPlum user to which the usage belongs. Data Type: String

Example

{"source": "iPlum", "clientReferenceId": "1234", "recordId": "ae0b4e3e-76c0-46d2-8bab-18c8764081dbgjdi2j", "phonenumber": "+14081231234", "dateTimeUTC": "2023-03-22T00:24:00Z", "duration": "3", "direction": "in", "length": "434.2 KB", "content": "http://media.iplum.com/ae0b4e3e-76c0-46d2-8bab-18c8764081db.mp3", "userId": "test@iplum.com"}

Contact Us

For any questions, please use the online form to send your ticket: https://iplum.com/contact-us-iplum/

Thank you!
Your payment has been received and will be process!
Oops! Something went wrong while submitting the form.