Track every update, improvement, and fix to the PerfexCRM API & Webhooks Module.
Scheduled webhook events now fire once per record, as documented, rather than repeating every day. task.overdue, invoice.overdue, estimate.expiring, contract.expiring and contract.expired were re-sent for every still-overdue or still-expiring record on every daily run — a site with 500 overdue invoices received 500 deliveries a day, indefinitely. They now fire once, at the moment a record becomes overdue or is about to expire, matching what the event catalogue has always described. Upgrading silently adopts the records that are already overdue or expiring, so you will not receive a backlog of historical events when you update. Recommended for everyone on 3.0.8 or earlier.
Restores three scheduled webhook events and the daily licence check. On installations running MySQL in strict mode (the default from MySQL 5.7 onward), a date comparison inside the scheduled-events job failed on every run, which meant task.overdue, invoice.overdue and estimate.expiring never fired. The same fault also stopped the daily licence check from completing, so your installed version and licence status had stopped reporting to the licence panel. Both are fixed. Separately, a freshly activated installation now shows up in the licence panel within seconds instead of up to a day later. Note: if you have webhooks configured for the three overdue/expiring events, they will begin delivering again on the next scheduled run. No configuration or migration needed.
Follow-up to v3.0.6 that irons out two edge cases in the new partial-update handling. Updating a lead while including the create-only contact-date fields no longer returns an error (those fields are simply ignored on update, as they only ever applied to creation), and cancelling recurring project tasks through a project update works again. No configuration or migration needed.
Partial updates no longer wipe fields you did not send. When you update a customer, lead, project or staff member through the API and only include the fields you are changing, everything else is now preserved: customer country/currency and group memberships, lead email/address/visibility, project dates, costs, settings and team members, and — importantly — a staff member’s full permission set, departments and admin status. (Previously, for example, updating only a staff member’s phone number silently cleared all their permissions.) The staff admin flag is now honored correctly on create and update. Separately, create and update requests that carry an unrecognized field (or a field echoed back from a GET, like tags) no longer fail with an empty HTTP 500 — the unknown field is simply ignored. Same bug class as v3.0.5, swept across every remaining resource. No configuration or migration needed.
Line items now work identically across all four sales resources. POST and PUT for estimates, proposals, credit notes and invoices all accept an items array (the same shape GET returns; newitems still works as an alias) — previously a bare items array crashed estimate and proposal creation with an empty HTTP 500 and was rejected or silently ignored on updates. Proposals and credit notes also gain automatic totals calculation from line items on create (they previously saved 0.00 shells). Adding or removing items on update recalculates subtotal, taxes and total, correctly re-basing a stored percentage discount. Partial updates no longer wipe fields you did not send: invoice due dates, billing address and recurrence settings, and proposal customer/lead links are all preserved. Unknown payload fields no longer cause crashes on create. Reported by the digiALERT security team (digialert.com) via support ticket #77. No configuration or migration needed.
Security and correctness hardening. Task attachment uploads are now validated against an allowed-file-type list (closing a file-upload risk), and webhook target URLs are protected against SSRF — requests to cloud-metadata and link-local addresses are always blocked, with an optional strict mode to also block private/LAN addresses. The webhook queue now claims each job atomically, so a delivery can never be sent twice under load. Also: login now has brute-force throttling, admin actions are CSRF-protected, and updating an estimate or invoice discount now recalculates the total correctly. All webhook processing modes are unchanged. No configuration or migration needed.
Async webhook delivery now works reliably. If you set the webhook processing mode to "Async", queued deliveries could previously sit in the queue without ever being sent: the documented cron command exited with an error before processing anything, and the built-in fallback deliberately skipped async mode. Both halves are fixed — the cron entrypoint now runs correctly (same command as before, no crontab changes needed), and installs without a configured cron automatically fall back to delivering queued webhooks on admin page loads. Immediate and Smart modes (the default) are completely unchanged. No configuration or migration needed.
Estimate and invoice totals now calculate themselves. POST /api/v1/estimates and POST /api/v1/invoices auto-calculate subtotal and total from your line items when you don't supply them (previously they were saved as 0.00 — Perfex trusts client-side totals, which only the admin UI computed). Discounts, adjustments and taxes are all folded in; explicitly supplied totals are still respected as-is. Also fixes two MCP tools that could never succeed: tasks_assign and tickets_assign declared the wrong parameter name and now correctly accept staff_id, and invoices_create now marks date as required to match the API. No configuration or migration needed.
Smarter update handling. Your installation now checks in with the license panel automatically about once a day (version information only) so update availability stays current without anyone clicking "Check for Updates". It is fire-and-forget and best-effort — it can never slow down or interrupt your admin. No configuration or migration needed.
Milestone release. Fixes POST /api/v1/invoices, which previously returned an HTTP 500 on every request — creating invoices through the API, with or without line items, now works correctly. The jump from 2.9.x to 3.0.0 is a version-numbering rollover, not a breaking change: your license, API keys, endpoints and webhooks all keep working exactly as before. No configuration or migration needed.
Delivery-reliability follow-up: a few webhook events (task.deleted, expense.deleted and estimate.created) could be sent more than once per action — they are now delivered exactly once. Also improved lead.created delivery for web-to-lead form submissions. No configuration or migration needed.
Reliability fixes for system API keys and webhook delivery. System API keys no longer fail with an empty HTTP 500 when creating tasks, subscriptions, expenses or notes — these endpoints now behave like every other resource. Repaired a set of webhook events that were not firing reliably: task.created, task.updated, task.deleted, lead.created, lead.converted, project.created, project.updated, staff.created, staff.updated, staff.deleted, expense.updated, expense.deleted and ticket.status_changed now deliver correctly, and a duplicate contract.created delivery was removed. No new endpoints or events — this release makes the existing 103-event catalog fire correctly.
Fix empty HTTP 500 responses on task write operations for system API keys — assigning a staff member to a task (POST /tasks/{id}/assign) and other task writes now succeed instead of failing silently. Also repaired the task.assigned webhook so it fires when a task is assigned through the API.
Updating now regenerates the module's API routes automatically — fixes Knowledge Base and Webhooks-management endpoints being unreachable on upgraded installs. Routes also self-heal on every admin load, so future updates can't leave stale routes behind. The MCP server now returns a clear error instead of an empty response when a route is unavailable. (Reported via support ticket #48.)
10 new Knowledge Base MCP tools — AI agents can now create, read, update, delete and list knowledge base articles and groups through the MCP server (knowledge_base_article_*, knowledge_base_group_* tools). MCP tool count is now 181. No configuration or migration needed.
Knowledge Base is now a first-class REST resource: full CRUD for articles and groups (GET/POST/PUT/DELETE /api/v1/knowledge-base and /knowledge-base/groups), filtering by group, and a dedicated knowledge_base permission scope for API keys. Adds 3 webhook events: knowledge_base_article.created, knowledge_base_article.updated, knowledge_base_article.deleted (103 events total).
AI agents can now discover and use list filters. The MCP list tools (estimates, invoices, payments, proposals, tasks, customers, contacts, subscriptions, timesheets, credit notes, notes, contracts, staff, leads) now advertise the date-range and entity filters the API already supported — so an assistant asked for "this quarter's estimates" or "unpaid invoices for customer X" filters server-side instead of pulling everything and mis-totaling. Also corrected a few misleading parameter names (invoices use from_date/to_date) and removed filters that never did anything. No configuration or migration needed.
Webhooks are now fully manageable through the REST API: list, create, read, update, delete, toggle active state, browse the event catalog, and view delivery logs (GET/POST /api/v1/webhooks, /webhooks/events, /webhooks/{id}, /webhooks/{id}/toggle, /webhooks/{id}/logs). This is the backend that powers the n8n PerfexCRM Trigger node, which now registers and verifies its webhooks automatically. Adds a dedicated webhooks permission scope for API keys.
Webhook delivery errors are now visible — a failed "Test Webhook" reports the real cause (SSL certificate problem, connection refused, HTTP 404, etc.) instead of a generic message, and the Webhook Logs screen adds an Error column showing exactly why a delivery failed. No configuration or migration needed.
New get_current_datetime MCP tool — gives AI agents the CRM's current date/time plus pre-computed reporting periods (today, year-to-date, current month/quarter, previous quarter) for accurate date-relative queries like overdue invoices, this-quarter estimates, or year-to-date reports. MCP tool count is now 171.
Custom fields are now fully readable and writable through the API across all 13 supported entities (customers, contacts, leads, projects, tasks, tickets, contracts, invoices, estimates, proposals, credit notes, expenses, staff). GET responses include a custom_fields object (slug => value), list endpoints accept ?include=custom_fields, and create/update accept a flat custom_fields map keyed by field id or slug. Unknown keys return a 400 with the list of valid fields. No schema migration required.
Fix MCP tool schema validation for estimates_create and credit_notes_create. Added required items sub-schema to array properties to pass OpenAI strict JSON Schema validation. Anthropic's API was lenient and accepted the bare array type; OpenAI (used by Hermes Agent via Codex) rejected it with HTTP 400. All 9 array types in MCP tools now have proper items sub-schemas.
Fix Claude.ai, Claude Desktop, and Claude Code only loading 50 of 170 MCP tools. Increased MCP tools/list page size from 50 to 250 so all tools load in a single request. Root cause: Anthropic MCP clients do not follow cursor-based pagination (nextCursor). All 170 tools including the full task management suite (22 tools) are now visible on first request.
Sub-resource pagination for Projects (tasks, milestones, files, discussions) and Invoices (payments). All sub-resource endpoints now support page and per_page/limit parameters (default 25, max 100) with full pagination metadata. Prevents oversized responses when projects have hundreds of tasks. Invoice payments pagination preserves total_paid/total_due summary in response metadata.
Fix Items API returning 401 for system API keys. Items_api.php used redundant user_id checks instead of requirePermission(), causing system keys (user_id=NULL) to be rejected even when fully authenticated. Replaced with standard permission pattern matching all other endpoints.
Fix blank/empty API response for system API keys (v2.8.3 regression). PerfexCRM core model methods internally call get_staff_user_id() which returned false when no session was set, producing invalid SQL and fatal TypeErrors on PHP 8.x. System API keys now impersonate the first active admin staff member in session context.
Fix system API keys denied access to all resources. System keys (user_id=NULL) were blocked by staff permission checks in getStaffVisibilityId(). Added early return to bypass visibility filtering for system keys, which have full access by design.
Fix route regeneration using safe string replacement instead of preg_replace (prevents regex corruption). Fix log_message crash when API response logger encounters non-string bodies. Fix undefined property warning in webhook delivery logging.
Fix query builder corruption in Projects, Invoices, and Leads endpoints caused by getStaffVisibilityId() resetting shared CI database state. Isolated visibility queries using independent DB connections to prevent cross-contamination of WHERE clauses.
MCP Server (Model Context Protocol) — AI agents can now discover and execute all 170 CRM operations via a single JSONRPC 2.0 endpoint. Works with Claude Desktop, ChatGPT, Cursor, n8n AI Agent, and 50+ MCP-compatible clients.
Permission-filtered tool discovery — AI agents only see operations the API key is authorized for. Built-in MCP documentation with curl examples, n8n AI Agent setup guide, and starter workflow template.
Admin MCP toggle in Settings > Advanced (disabled by default for security). Streamable HTTP transport with SSE support for real-time AI agent communication.
Staff-level permission filtering for all API endpoints. Non-admin staff members now only see data they have permission to access, matching PerfexCRM's native permission system. Applied to 44 methods across Projects, Leads, Tasks, Invoices, and Estimates — covering LIST, GET, UPDATE, DELETE, and all sub-resource operations. Uses PerfexCRM core permission functions (staff_can, user_can_view_invoice, user_can_view_estimate, staff_can_access_lead).
New global ?fields= query parameter for column-level response filtering. Request only the fields you need (e.g. ?fields=id,company,phonenumber) to reduce payload size. Applied automatically to all endpoints via the response layer — zero endpoint changes needed. Always includes id field. Also fixes route regeneration on update to ensure all (:num) endpoints are registered on existing installations.
New ?include= query parameter for all list endpoints. Related data (members, items, client, totals, etc.) is now opt-in, reducing default response size by up to 90% and eliminating N+1 query overhead. Affected endpoints: Projects, Invoices, Leads, Contracts, Estimates, Proposals, Credit Notes. Single-resource GET endpoints unchanged for backward compatibility.
Fix API 500 error on Projects and 12 other endpoints that use PerfexCRM core models. Root cause: get_staff_user_id() returned false in API context because no session was set, generating invalid SQL with empty IN() clauses. Fix: set PerfexCRM session context (staff_user_id, staff_logged_in) after API authentication so all core model methods work correctly. Subdomain matching for license activation: subdomains of an activated domain are now automatically recognized without consuming an additional activation slot.
Fix Auth_api.php: 17 broken response calls causing 500 errors on all auth endpoints (login, refresh, verify). Fix CI3 Session driver 503 error on Linux servers (defensive constructor workaround). Remove legacy Api.php catch-all router (root cause of Session 503 bug). Extract duplicate JWT generation into reusable buildJwt() method. Refactor routes from catch-all pattern to explicit per-endpoint routing. Remove duplicate lowercase auth_api.php file (Linux case-sensitivity fix).
Fix API response time always showing 0.000ms (protected → public $start_time). Server-side DataTables pagination for API and webhook logs. Add Clear All Logs buttons (admin-only) for API and webhook logs. Remove "All" option from log table length menu (memory safety). Display log retention info on logs page. Fix Auth_api.php case sensitivity for Linux server compatibility. Fix CSRF token missing on Regenerate Routes button (419 error).
Fixed ticket replies list still causing memory exhaustion. Root cause: SELECT * was loading full HTML email bodies with base64-encoded inline images. List replies now returns metadata only (id, date, sender). Full message content available via individual reply endpoint GET /tickets/{id}/replies/{replyId}.
Fixed ticket replies endpoint causing PHP memory exhaustion (512MB) on tickets with many or large replies. Added pagination support (page, per_page) to GET /tickets/{id}/replies. Embedded replies via ?include=replies now capped at 25 with reply_count metadata.
Fixed "Save Changes" button not working on Settings page. Root cause: nested HTML form for "Regenerate Routes" inside the main settings form (invalid HTML). Browser closed the outer form at the inner closing tag, leaving the Save button outside any form. Replaced nested form with JavaScript-based POST submission.
API completeness: Added POST /invoices (create invoice with full line items support) and POST /contracts (create contract). Added invoice.deleted and staff.login webhook events (100 total). Fixed ticket assign/status endpoints to accept both PUT and POST methods. n8n node aligned with API changes.
Fixed "Download Postman Collection" button returning 404 on production installations. Postman collection (193 requests, 21 resource folders) now included in distribution ZIP. Collection version bumped to v2.5.4
Standardized pagination across all 19 controllers: migrated Invoices, Leads, Projects, Contracts to getPaginationParams() helper. Added limit as backwards-compatible alias for per_page. Webhook docs enhanced with click-to-expand descriptions and payload field tags for all 98 events. 518 E2E tests passing (301 API + 217 webhook)
Documentation audit (21 fixes): Removed 4 ghost KB endpoints from admin docs, added JWT auth endpoint docs, added Resource Details for 12 API resources, added pagination/sorting docs. Postman collection updated to v2.5.1 with broken webhook test removed and 5 task operations added. README/COMPETITIVE-ANALYSIS updated with current numbers. Added 11 missing webhook event category language strings. 526 E2E tests passing (306 API + 220 webhook)
Auto-update security hardening: OPcache bypass for reliable file reads, concurrent update lock protection, download URL SSRF prevention, Throwable catch blocks for broader error handling. 301 E2E tests passing
Activation page security hardening: POST-only guards with CSRF tokens, XSS prevention on all dynamic outputs, download URL domain validation (MITM protection), double-submit prevention with loading spinners. License model instance-level caching. Removed dead KB routes. Restored contract.renewed webhook event. Expanded Postman collection. Critical routing and security fixes across all 5 new resources from v2.4.0. 526 E2E tests passing (306 API + 220 webhook)
Activation page security hardening: POST-only guards with CSRF tokens, XSS prevention on all dynamic outputs, download URL domain validation (MITM protection), double-submit prevention with loading spinners. License model instance-level caching (7 DB queries reduced to 1). Removed 4 dead KB routes. Restored contract.renewed webhook event (98 total). Postman collection expanded with Auth section and Invoice/Contract CRUD. 526 E2E tests passing (306 API + 220 webhook)
Removed OAuth 2.0 skeleton code (DB tables, model methods, auth chain, routes — never implemented). Settings page redesigned with 3-tab layout and conditional field visibility. Added Response Options (include_meta, include_timestamps) to settings UI. Fixed cache options not seeded in install. Fixed 12 input fields missing id attributes for label association. Fixed max_request_size input missing max attribute. Code cleanup across 29 files. 477 E2E tests passing (287 API + 190 webhook)
Dashboard & logs audit: Fixed XSS in all log views and dashboard, SQL performance optimization with 3 compound indexes, ISO week format fix, N+1 query elimination, MVC compliance (views no longer query DB directly), removed dead code, fixed exception message leak in Admin controller, fixed migration DB access pattern. 25 new localization keys. 477 E2E tests passing (287 API + 190 webhook)
Security & reliability fixes: API key management (expires_at saving, edit page display, per-key rate limiting, permissions list), webhook management (URL/event validation, retry enforcement, XSS prevention, secret standardization, header safety), ZIP build fix for views/logs/ directory. 469 E2E tests passing
Critical routing fix for v2.4.0 resources. Security fixes: notes permission bypass, items auth checks. Quality fixes: invoice status sync on payments, cascade deletes, input validation, cache wildcard support, lead.status_changed accuracy. 469 E2E tests passing (282 API + 187 webhook)
5 new standalone API resources: Payments, Items (full CRUD upgrade), Contacts, Timesheets, Notes. 15 new webhook events (3 per resource). Notes support polymorphic relations across 10 entity types. Timesheets support running timer concept. Updated Postman collection with 26 new requests. 282 E2E API tests passing + webhook tests. Total: 19 resources, 170+ endpoints, 98+ webhook events
Test update workflow
Auto-update download workflow verification
Update workflow verification release
Fix install update "Invalid request" error
Update verification release
Fix changelog display, file size extraction, version downgrade guard
Updated README with full feature overview
Release pipeline skill validation
Clear PHP opcache after auto-update for shared hosting
One-click auto-update feature
Security hardening, dead code cleanup
Security improvements
Initial public release
If you have questions about a specific release or need help upgrading, our support team is here to assist.