Webhook Notifications - Getting Started Guide

Webhook Notifications - Getting Started Guide

Webhook Notifications - Getting Started Guide 

What are Webhooks? 

Webhooks are automatic notifications sent to your server when something happens in Loft. Instead of constantly checking Loft for updates, Loft will "push" the information to you instantly.

Think of it like this: instead of refreshing your email every 5 minutes, you get a notification on your phone the moment an email arrives.


Step 1: Set Up Your Webhook Endpoint 

Before configuring Loft, you need a server that can receive HTTP POST requests. For testing, you can use free services like:

For production, you'll need your own server endpoint (e.g., https://yourcompany.com/webhooks/loft).


Step 2: Configure Webhook in Loft 

  1. Go to Settings → Notification Preferences
  2. Find the Webhook Settings section
  3. Enter your Webhook URL (e.g., https://webhook.site/your-unique-id)
  4. Click Save
  5. Loft will automatically generate a Webhook Secret - copy and save this somewhere safe!

⚠️ Important: The secret is only shown once. If you lose it, you'll need to click "Regenerate" to get a new one.


Step 3: Enable Webhooks for Specific Events 

After setting up your URL, you need to choose which notifications should trigger webhooks:

  1. In Notification Preferences, find the notification types you want (e.g., "Comments", "Approvals")
  2. Check the Webhook checkbox for each one
  3. Save your preferences

What You'll Receive 

When an event occurs, Loft sends a POST request to your URL with this structure:

{
"event": "notification",
"timestamp": "2026-01-20T12:34:56Z",
"data": {
"subject": "New comment on Deal #1234",
"body": "John Doe left a comment: Looking good!",
"action": "comment",
"actor": {
"id": 123,
"name": "John Doe",
"email": "john@example.com"
},
"deal": {
"id": 456,
"full_deal_number": "1234",
"deal_identifier": "123 Main Street"
}
}
}

Headers Included 

HeaderDescription
Content-Typeapplication/json
X-Loft-SignatureHMAC-SHA256 signature to verify the request is from Loft

Integrating with Slack 

You can send Loft notifications directly to a Slack channel. Since Slack expects a specific payload format, you'll need to set up a small middleware to transform the data.

Option 1: Using Zapier (No Code) 

  1. Create a Zapier account
  2. Create a new Zap:
    • Trigger: Webhooks by Zapier → Catch Hook
    • Action: Slack → Send Channel Message
  3. Copy the Zapier webhook URL and paste it in Loft's Webhook URL field
  4. Map the fields:
    • Message Text: Use data.subject and data.body from the payload
    • Channel: Select your desired Slack channel
  5. Turn on your Zap

Option 2: Using a Simple Server 

If you prefer more control, create a small server that transforms Loft webhooks to Slack format.

Step 1: Create a Slack Incoming Webhook 

  1. Go to Slack API: Incoming Webhooks
  2. Click Create your Slack app (or use an existing app)
  3. Enable Incoming Webhooks
  4. Click Add New Webhook to Workspace
  5. Select the channel where you want notifications
  6. Copy the Webhook URL (looks like https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX)

Step 2: Create a Middleware Server 

Here's an example using Node.js/Express:

const express = require('express');
const crypto = require('crypto');
const fetch = require('node-fetch');

const app = express();
app.use(express.json());

const LOFT_WEBHOOK_SECRET = process.env.LOFT_WEBHOOK_SECRET;
const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL;

function verifySignature(payload, signature) {
const expected = crypto
.createHmac('sha256', LOFT_WEBHOOK_SECRET)
.update(JSON.stringify(payload), 'utf8')
.digest('hex');
return signature === expected;
}

app.post('/webhooks/loft-to-slack', async (req, res) => {
const signature = req.headers['x-loft-signature'];

// Verify the webhook is from Loft
if (!verifySignature(req.body, signature)) {
return res.status(401).send('Invalid signature');
}

const { data } = req.body;

// Transform to Slack format
const slackMessage = {
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: data.subject,
emoji: true
}
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: data.body
}
},
{
type: 'context',
elements: [
{
type: 'mrkdwn',
text: `*From:* ${data.actor?.name || 'System'} | *Deal:* ${data.deal?.deal_identifier || 'N/A'}`
}
]
}
]
};

// Send to Slack
await fetch(SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(slackMessage)
});

res.status(200).send('OK');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Step 3: Deploy and Configure 

  1. Deploy your server (e.g., Heroku, Render, AWS Lambda)
  2. Set the environment variables:
    • LOFT_WEBHOOK_SECRET - Your secret from Loft
    • SLACK_WEBHOOK_URL - Your Slack incoming webhook URL
  3. Copy your server's URL and paste it in Loft's Webhook URL field

Example Slack Message 

Your Slack notifications will look like this:

┌─────────────────────────────────────────────┐
│ New comment on Deal #1234
├─────────────────────────────────────────────┤
│ John Doe left a comment: Looking good!
│ │
│ From: John Doe | Deal: 123 Main Street │
└─────────────────────────────────────────────┘

Verifying the Signature (Important) 

The X-Loft-Signature header lets you verify that the webhook actually came from Loft and wasn't spoofed by someone else.

How it works: 

  1. Loft takes the JSON payload
  2. Signs it using your Webhook Secret with HMAC-SHA256
  3. Sends the signature in the X-Loft-Signature header

Verifying with cURL (for testing) 

You can manually verify a signature using this command:

# Your webhook secret (from Loft settings)
SECRET="your-webhook-secret-here"

# The exact JSON payload you received (must be exact, no extra spaces)
PAYLOAD='{"event":"notification","timestamp":"2026-01-20T12:34:56Z","data":{"subject":"Test"}}'

# Generate the expected signature
echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET"

This outputs something like:

SHA2-256(stdin)= a1b2c3d4e5f6...

Compare this with the X-Loft-Signature header value - they should match!


Code Examples for Signature Verification 

Node.js 

const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');

return signature === expectedSignature;
}

// In your Express route:
app.post('/webhooks/loft', (req, res) => {
const signature = req.headers['x-loft-signature'];
const payload = JSON.stringify(req.body);

if (!verifyWebhook(payload, signature, process.env.LOFT_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}

// Process the webhook
console.log('Received:', req.body);
res.status(200).send('OK');
});

Python 

import hmac
import hashlib

def verify_webhook(payload: str, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)

# In your Flask route:
@app.route('/webhooks/loft', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Loft-Signature')
payload = request.get_data(as_text=True)

if not verify_webhook(payload, signature, os.environ['LOFT_WEBHOOK_SECRET']):
return 'Invalid signature', 401

data = request.json
print(f"Received: {data}")
return 'OK', 200

Ruby 

require 'openssl'

def verify_webhook(payload, signature, secret)
expected = OpenSSL::HMAC.hexdigest('SHA256', secret, payload)
Rack::Utils.secure_compare(signature, expected)
end

# In your Rails controller:
def webhook
signature = request.headers['X-Loft-Signature']
payload = request.raw_post

unless verify_webhook(payload, signature, ENV['LOFT_WEBHOOK_SECRET'])
return head :unauthorized
end

data = JSON.parse(payload)
Rails.logger.info "Received webhook: #{data}"
head :ok
end

PHP 

<?php
function verifyWebhook($payload, $signature, $secret) {
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}

// In your webhook handler:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_LOFT_SIGNATURE'] ?? '';
$secret = getenv('LOFT_WEBHOOK_SECRET');

if (!verifyWebhook($payload, $signature, $secret)) {
http_response_code(401);
die('Invalid signature');
}

$data = json_decode($payload, true);
error_log("Received webhook: " . print_r($data, true));
http_response_code(200);
echo 'OK';

Troubleshooting 

"I'm not receiving webhooks" 

  1. Check that your Webhook URL is saved in Loft settings
  2. Verify the Webhook checkbox is enabled for the notification type
  3. Make sure your server is publicly accessible (not localhost)
  4. Check your server logs for incoming requests

"Signature verification is failing" 

  1. Make sure you're using the exact payload as received (no modifications)
  2. Don't pretty-print or reformat the JSON before verifying
  3. Ensure your secret doesn't have extra whitespace
  4. Check that you're using HMAC-SHA256 (not SHA1 or MD5)

"I lost my webhook secret" 

  1. Go to Settings → Notification Preferences → Webhook Settings
  2. Click Regenerate to create a new secret
  3. Update your server with the new secret

"Slack messages aren't appearing" 

  1. Verify your Slack Incoming Webhook URL is correct
  2. Check that your middleware server is running and accessible
  3. Test your Slack webhook directly with cURL:
    curl -X POST -H 'Content-Type: application/json' \
    --data '{"text":"Test message"}' \
    YOUR_SLACK_WEBHOOK_URL
  4. Check your middleware server logs for errors

Quick Reference 

ItemValue
HTTP MethodPOST
Content-Typeapplication/json
Signature HeaderX-Loft-Signature
Signature AlgorithmHMAC-SHA256
Timeout30 seconds

That's it! You're now ready to receive real-time notifications from Loft via webhooks.

    • Related Articles

    • Webhook Notifications - Getting Started Guide

      Webhook Notifications - Getting Started Guide What are Webhooks? Webhooks are automatic notifications sent to your server when something happens in Loft. Instead of constantly checking Loft for updates, Loft will "push" the information to you ...
    • Getting Started with PAYLOAD

      In Article Navigation can be found to the right of this article or via the buttons below Fees Set Permissions Setup Account Map Account Loft47 has embedded the payload.co payment gateway to offer direct payouts to agents for their commission on ...
    • Getting Started with ZUM RAILS

      Zum Rails offers an excellent alternative for paying and receiving funds. Now available for both our USA and Canadian clients! They provide low transaction rates and a dedicated portal to expand and enhance your options for Agent billing. They offer ...
    • Managing your Notification Settings

      To manage or change which notifications you're subscribed to and how you'd like to receive them, click the dropdown arrow next to your name in the top right of the app and then click Settings. Notifications can be received in three ways: Please note, ...
    • Guide to Agent47 Subscription

      As an Agent you have a lot to juggle. Generating leads, closing deals and building your brand are just a few which rank at the top of the list.  While accounting isn't your top priority, it ultimately has a high impact on your overall success.   With ...