Webhook Signature Verification

This explains how to verify webhooks requests being sent from Luxury Presence.

Overview

This guide explains how to use the Luxury Presence Webhooks API. The Webhooks API can be used to subscribe to receive real-time Lead Activity events originating from actions taken on your Luxury Presence Websites.

Setup

See our documentation Webhooks API reference for listing, creating and deleting webhook subscriptions.

Receiving requests

All outbound webhook requests from webhook-lambda include HMAC-SHA256 signatures to ensure authenticity and integrity. Recipients should verify these signatures before processing webhook payloads.
The signature headers sent in the requests can be used to verify the origin of these requests as coming from Luxury Presence. See the section below for more information.

Signature Headers

Each webhook request includes the following headers:

  • x-lp-signature: The HMAC-SHA256 signature in hexadecimal format
  • x-lp-timestamp: Unix timestamp (in milliseconds) when the signature was generated
  • x-lp-signature-version: Signature algorithm version (currently "v1")

Signature Generation

The signature is generated using the following process:

  1. Create a string to sign by concatenating: timestamp + url + JSON.stringify(payload)
  2. Generate HMAC-SHA256 signature using your shared secret
  3. Convert the signature to hexadecimal format

Payload format

Currently, we only support Lead activity sync through webhooks. See Webhook Events for more information on what action triggers each activity type. Payload follows the example structure:

{  
    "eventName": "leads",  
    "companyId": "17g498eh-5b11-4d1c-8448-cb06ef213730",  
    "data": {
        "leadId": "1d53e414-c8ad-49b8-9e23-2046f846d3d9",
        "leadEmail": "[email protected]",
        "leadFirstName": "Fluffy",
        "leadLastName": "Trang",
        "leadPhoneNumber": "(718) 555-5555",
        "leadSource": "HOME_SEARCH",
        "leadOrigin": "HOME_SEARCH",
        "activityType": "ADD_FAVORITE",
        "activityListingId": "b8fb82d6-03f4-42f3-ab57-526493b60949",
        "activityAction": "ADD_FAVORITE",
        "activityMessage": "",
        "activityListingMlsId": "RLS30036941",
        "activityListingAddress": "52 JANE Street 20",
        "activityListingCity": "New York City",
        "activityListingState": "NY",
        "activityListingZip": "10014",
        "activitySourceUrl": "p-110e844c-6254-4378-a176-f04ed117be61.staging.presencepreview.site/home-search/listings/b8fb82d6-03f4-42f3-ab57-526493b60949",
        "assignedAgents": [
            \{
                "firstName": "",
                "lastName": "",
                "email": "[email protected]",
                "mlsAgentId": ""
            }
        ],
        "assignedAgent": {
            "firstName": "",
            "lastName": "",
            "email": "[email protected]",
            "mlsAgentId": ""
        },
        "isNewLead": false,
        "newPhoneNumber": false,
        "companyName": "The Aster Group",
    }
}


Verification Example (Node.js)

const crypto = require('crypto');

function verifyWebhookSignature(url, timestamp, signature, payload, secret) {
  // Recreate the string that was signed
  const stringToSign = timestamp + url + JSON.stringify(payload);

  // Generate expected signature
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(stringToSign)
    .digest('hex');

  // Compare signatures using timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expectedSignature, 'hex')
  );
}