Phone Number Webhooks

Phone Number Webhook Configuration

This section explains how to receive incoming SMS for a specific phone number at your own webhook URL, and how to verify that each request came from Easify. Phone number webhooks are managed only through the API and do not appear in the Easify app.

This webhook is separate from the Incoming SMS webhook in API Settings. If you configure both, you receive both.

Adding a Webhook URL

Steps to Add a Webhook:
  1. Call phone-numbers and note the id of the number.
  2. Call phone-numbers/update with phone_number_id and your webhook_url.
  3. Save the webhook_secret from the response and keep it private, like a password. You can also get it later from the phone-numbers API.
Webhook Structure:

When an SMS or MMS is received on the number, Easify sends a POST request to your webhook URL with the following headers and body:

Headers
  • X-Easify-Signature: A Base64-encoded signature of the payload, generated using the number's webhook_secret. Not sent if the number has no secret.
  • X-Easify-Timestamp: The UNIX timestamp of the request.
Payload

The POST body contains the message data. Example:

{
  "message": "Hai.",
  "to_number": "+1864400****",
  "from_number": "+1234567***",
  "media_url": null,
  "lead_id": null,
  "status": "Incoming",
  "email": null,
  "full_name": null
}
  • to_number is your Easify number and from_number is the sender.
  • media_url is set for an MMS, otherwise null.
  • lead_id is the most recent lead_id you sent to this sender through the API from this number, if any.
  • email and full_name come from your saved contact for this sender, if there is one.

Your endpoint should return a 2xx status code. Any other response counts as a failed delivery.

Steps for Validating the Signature

  1. Extract Header and Payload Information

X-Easify-Signature and X-Easify-Timestamp from the request headers, and the raw JSON payload from the request body.

Read the body as raw bytes. Re-serialising a payload you have already parsed can reorder keys or change escaping, and the signature will no longer match.

  1. Recreate the Signature

Compute an HMAC-SHA256 of the raw payload using the number's webhook_secret. The digest is then written as a lowercase hex string, and it is that hex string which is Base64-encoded. Base64-encoding the raw digest bytes instead is the most common cause of a signature that never matches.

  1. Compare the Signatures

Compare your computed value against the one received in the X-Easify-Signature header using a constant-time comparison.

  1. Validate the Timestamp

To prevent replay attacks, ensure the timestamp in X-Easify-Timestamp is within an acceptable range of the current time (±15 minutes / 900 seconds).

The examples below follow these steps. Use this number's webhook_secret as the secret.

Validate the webhook signature
php

// Get the raw request body.
// Use the original body for signature verification without modifying it.
$rawBody = file_get_contents('php://input');

// Read the signature and timestamp sent in the webhook headers.
$receivedSignature = $_SERVER['HTTP_X_EASIFY_SIGNATURE'] ?? '';
$receivedTimestamp = (int) ($_SERVER['HTTP_X_EASIFY_TIMESTAMP'] ?? 0);

// This phone number's webhook_secret, returned by the phone-numbers API
$webhookSecret = getenv('EASIFY_WEBHOOK_SECRET');

// Reject requests older than 15 minutes to prevent replay attacks.
if (abs(time() - $receivedTimestamp) > 900) {
    http_response_code(408);
    exit;
}

// Generate the expected signature using the webhook secret.
$computedSignature = base64_encode(hash_hmac('sha256', $rawBody, $webhookSecret));

// Compare the received signature with the expected signature.
if (!hash_equals($computedSignature, $receivedSignature)) {
    http_response_code(403);
    exit;
}

// Signature is valid, now decode and handle the event.
$payload = json_decode($rawBody, true);

// ... handle the event ...

http_response_code(200);

Managing the Webhook

Failing Webhooks

If your endpoint accepts nothing for 24 hours and at least 50 deliveries fail, Easify emails you a warning once a day. After three warnings, the webhook is paused. Messages received while it is paused are not sent later. To resume it, call phone-numbers/update with your webhook URL.