A powerful webhook integration module for SuiteCRM 8.x that enables real-time data synchronization and event-driven automation.
cubicFusion SuiteCRM Webhooks is an enhanced fork of the original SuiteCRMWebHooks project from SidorkinAlex. Fixed, updated and maintained for modern SuiteCRM and PHP environments.
This module allows your SuiteCRM instance to automatically send HTTP POST requests to external endpoints whenever specific events occur, enabling seamless integration with third-party applications, automation platforms, and custom services.
The module is provided as is and will evolve as needed. Currently running it in combination with an n8n setup (n8n-nodes-suitecrm-community). Checkout the video from Bastian Hammer’s @ Youtube, for the initial setup and a complete overview.
Key Features
- Modern Compatibility: Fully compatible with SuiteCRM 8.8+ and PHP 8.3
- Flexible Configuration: Easy-to-use admin interface for setting up webhook endpoints
- Custom Headers Support: Configure custom HTTP headers including authentication tokens, API keys, and cache control
- JSON Encoding: Properly formatted JSON payloads with default bean properties
- Real-time Triggers: Instant webhook firing on CRM events like record creation, updates, and deletions







Plans
High Priority
- Further code cleanup / restructuring of the codebase / comments / file header comment for tracking changes + attribution
- Cleanup admin interface / add help popovers and descriptions
- HMAC signature
Low priority
- Replace predefened Key / Value pairs, with dynamic Key / Value pairs (breaking change). Did a quick proof of concept, which might make it into v0.3.x or later ;)
- Preload with some optional demo data
- Make a demo video :)
- Possibly add an option to handle incoming webhooks in the future. Currently using n8n (n8n-nodes-suitecrm-community) or a standalone solution connecting via the SuiteCRM API.
Nice to have
- Nothing yet ;)
Current Status
master branch / main development & updates
module_build branch / direct module packaging & releases (only updated when ready)
v0.1.1-alpha
- Fixed JSON encoding for webhook POST request
- Updated screenshots
- Tested with SuiteCRM 8.9
- Further cleanup
- Add default bean properties to the data send out
- Add Header options. Headers are extracted from the fields array and merged with the default content-type header. Some examples:
- Authorization: Bearer YOUR_ACCESS_TOKEN
- X-Custom-Header: Value
- Cache-Control: no-cache
- X-Rate-Limit: 1000
- X-API-Key: your-api-key
v0.1.0-alpha
- Fixed Fatal Errors with PHP 8.3 and SuiteCRM 8.8
- Fixed Language Files
- Initial cleanup
This project is in active alpha development with regular updates and improvements. The module has been tested with SuiteCRM 8.9 and PHP 8.3.
FAQ – Webhooks
What is a webhook?
How do webhooks differ from APIs?
How do I set up a webhook?
- Create an endpoint URL on your server to receive webhook data
- Configure the webhook in the source application by providing your endpoint URL
- Specify which events should trigger the webhook
- Implement proper security measures like HTTPS and signature verification
- Test the webhook to ensure it’s working correctly
What are the security best practices for webhooks?
Why are my webhooks failing or not being received?
What HTTP status codes should my webhook endpoint return?
How do webhook retries work?
How do I verify webhook authenticity using signatures?
- Get the signature from the header
- Calculate your own signature using the payload and your secret key
- Compare signatures using constant-time comparison to prevent timing attacks
What data format do webhooks typically use?
How do I handle webhook duplicates and replay attacks?
- Using unique idempotency keys provided in webhooks
- Storing processed webhook IDs to detect duplicates
- Checking timestamps to reject old requests
- Implementing a time window for valid requests (e.g., 5 minutes)
Should I process webhooks synchronously or asynchronously?
How do I test webhooks during development?
What are common webhook vulnerabilities?
How do I handle webhook rate limiting and high volumes?
- Using message queues to buffer incoming webhooks
- Processing webhooks in batches
- Implementing backpressure mechanisms
- Using multiple worker processes
- Setting up proper monitoring and alerting
- Communicating with providers about expected volumes and rate limits
What should I do if webhook endpoints are being hit by bots or spam?
How do I debug webhook issues?
- Check webhook provider’s delivery logs
- Implement comprehensive logging on your endpoint
- Verify SSL certificate validity
- Test endpoint accessibility externally
- Check response times and status codes
- Validate request signatures
- Monitor for network connectivity issues
- Use webhook testing tools to simulate requests
What’s the difference between webhooks and WebSockets?
How do I scale webhook processing for high traffic?
What should I include in webhook payload validation?
How long should webhook URLs remain valid?
FAQ – SuiteCRM
What is SuiteCRM?
How do I install SuiteCRM?
What are the system requirements for SuiteCRM?
How do I fix permission issues during installation?
How do I upgrade from SuiteCRM 7 to SuiteCRM 8?
How do I create custom modules in SuiteCRM?
How do I configure email in SuiteCRM?
How do I use the SuiteCRM API?
How do I backup SuiteCRM?
Why is my SuiteCRM installation showing a blank page?
How do I customize SuiteCRM in an upgrade-safe way?
How do I fix database connection errors?
How do I set up workflows in SuiteCRM?
How do I import data into SuiteCRM?
What’s the difference between SuiteCRM 7 and SuiteCRM 8?
How do I troubleshoot performance issues?
How do I get support for SuiteCRM?
Can I migrate SuiteCRM to a different server?
How do I configure LDAP authentication?
How do I create relationships between modules?
HMAC Signature example server / client lib in PHP
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
<?php /** * Simple HMAC Webhook Signature Library * * This library provides functionality for: * - Generating HMAC signatures for outgoing webhooks (client) * - Verifying HMAC signatures for incoming webhooks (server) */ class HmacWebhookSignature { private string $secret; private string $algorithm; private string $headerName; /** * Initialize the HMAC signature handler * * @param string $secret The shared secret key * @param string $algorithm Hash algorithm (default: sha256) * @param string $headerName Header name for signature (default: X-Signature-256) */ public function __construct( string $secret, string $algorithm = 'sha256', string $headerName = 'X-Signature-256' ) { $this->secret = $secret; $this->algorithm = $algorithm; $this->headerName = $headerName; } /** * Generate HMAC signature for payload * * @param string $payload The webhook payload * @param bool $includePrefix Whether to include algorithm prefix (e.g., "sha256=") * @return string The HMAC signature */ public function generateSignature(string $payload, bool $includePrefix = true): string { $signature = hash_hmac($this->algorithm, $payload, $this->secret); return $includePrefix ? $this->algorithm . '=' . $signature : $signature; } /** * Verify HMAC signature against payload * * @param string $payload The webhook payload * @param string $signature The signature to verify * @return bool True if signature is valid */ public function verifySignature(string $payload, string $signature): bool { $expectedSignature = $this->generateSignature($payload, true); // Use hash_equals to prevent timing attacks return hash_equals($expectedSignature, $signature); } /** * Get the header name for signatures * * @return string */ public function getHeaderName(): string { return $this->headerName; } } /** * Webhook Client - For sending webhooks with HMAC signatures */ class WebhookClient { private HmacWebhookSignature $hmac; public function __construct(HmacWebhookSignature $hmac) { $this->hmac = $hmac; } /** * Send webhook with HMAC signature * * @param string $url Webhook URL * @param array $data Payload data * @param array $headers Additional headers * @return array Response with status and body */ public function sendWebhook(string $url, array $data, array $headers = []): array { $payload = json_encode($data); $signature = $this->hmac->generateSignature($payload); $defaultHeaders = [ 'Content-Type: application/json', 'User-Agent: PHP-Webhook-Client/1.0', $this->hmac->getHeaderName() . ': ' . $signature ]; $allHeaders = array_merge($defaultHeaders, $headers); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => $allHeaders, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_FOLLOWLOCATION => true, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); return [ 'success' => $httpCode >= 200 && $httpCode < 300, 'status_code' => $httpCode, 'response' => $response, 'error' => $error ]; } } /** * Webhook Server - For receiving and verifying webhooks with HMAC signatures */ class WebhookServer { private HmacWebhookSignature $hmac; public function __construct(HmacWebhookSignature $hmac) { $this->hmac = $hmac; } /** * Verify incoming webhook request * * @param string|null $payload Raw payload (if null, will read from php://input) * @param string|null $signature Signature header (if null, will read from headers) * @return bool True if signature is valid */ public function verifyIncomingWebhook(?string $payload = null, ?string $signature = null): bool { if ($payload === null) { $payload = file_get_contents('php://input'); } if ($signature === null) { $signature = $this->getSignatureFromHeaders(); } if (!$signature) { return false; } return $this->hmac->verifySignature($payload, $signature); } /** * Get signature from HTTP headers * * @return string|null */ private function getSignatureFromHeaders(): ?string { $headerName = $this->hmac->getHeaderName(); // Try different header name formats $possibleHeaders = [ 'HTTP_' . str_replace('-', '_', strtoupper($headerName)), strtoupper($headerName), $headerName ]; foreach ($possibleHeaders as $header) { if (isset($_SERVER[$header])) { return $_SERVER[$header]; } } // Also check getallheaders() if available if (function_exists('getallheaders')) { $headers = getallheaders(); foreach ($headers as $name => $value) { if (strtolower($name) === strtolower($headerName)) { return $value; } } } return null; } /** * Handle webhook request with callback * * @param callable $callback Function to call if webhook is valid * @param bool $respondWithStatus Whether to send HTTP status responses * @return mixed Callback return value or null */ public function handleWebhook(callable $callback, bool $respondWithStatus = true) { if (!$this->verifyIncomingWebhook()) { if ($respondWithStatus) { http_response_code(401); header('Content-Type: application/json'); echo json_encode(['error' => 'Invalid signature']); } return null; } $payload = file_get_contents('php://input'); $data = json_decode($payload, true); try { $result = $callback($data, $payload); if ($respondWithStatus) { http_response_code(200); header('Content-Type: application/json'); echo json_encode(['status' => 'success']); } return $result; } catch (Exception $e) { if ($respondWithStatus) { http_response_code(500); header('Content-Type: application/json'); echo json_encode(['error' => 'Internal server error']); } throw $e; } } } // Example usage: /* // CLIENT SIDE - Sending webhooks $hmac = new HmacWebhookSignature('your-secret-key'); $client = new WebhookClient($hmac); $webhookData = [ 'event' => 'user.created', 'user_id' => 123, 'timestamp' => time() ]; $response = $client->sendWebhook('https://example.com/webhook', $webhookData); if ($response['success']) { echo "Webhook sent successfully!\n"; } else { echo "Failed to send webhook: " . $response['error'] . "\n"; } // SERVER SIDE - Receiving webhooks $hmac = new HmacWebhookSignature('your-secret-key'); $server = new WebhookServer($hmac); $server->handleWebhook(function($data, $rawPayload) { // Process the webhook data error_log("Received webhook: " . json_encode($data)); // Your webhook processing logic here switch ($data['event']) { case 'user.created': // Handle user creation break; case 'user.updated': // Handle user update break; } return ['processed' => true]; }); // Or manual verification: if ($server->verifyIncomingWebhook()) { $payload = file_get_contents('php://input'); $data = json_decode($payload, true); // Process webhook... } else { http_response_code(401); echo "Invalid signature"; } */ |
