Change Document: v2.2.1.0 — Permission Fix for Customer Creation & Transaction Scope Alignment
| Field | Value |
|---|---|
| Version | 2.2.1.0 |
| Date | 2026-03-05 |
| Extension | BC Dialing Application (Cambay Solutions) |
| Severity | Critical — 65% of incoming API calls failing |
| Status | Implemented — pending deployment |
Background
The v2.2.0.0 release (deployed to Production on March 4, 2026) changed Customer creation in API Pages 80000 (ReceivePhoneNumber) and 80003 (ReceiveEmail) from Insert() to Insert(true) to properly fire the standard BC OnInsert trigger. This was the correct fix — Insert() without triggers was bypassing No. Series validation, default field population, and Contact creation.
However, Insert(true) invokes the full Customer OnInsert trigger chain, which accesses standard BC tables that the API pages did not have permissions for. The inline Permissions property on both pages only covered Customer, CustomerPhoneLog/CustomerEmailLog, and NextivaConfig. The OnInsert trigger also accesses:
- No. Series and No. Series Line — for customer number assignment
- Sales & Receivables Setup — for default settings
- Marketing Setup, Contact, Contact Business Relation, Business Relation — for automatic Contact creation
- Customer Templ. — for template lookup
- Default Dimension, Dimension Value — for template dimension application
- Customer Posting Group, Gen. Business Posting Group, VAT Business Posting Group — for template field validation
Without permissions on these tables, every API call that reached the Insert(true) path failed with NavPermissionException (HTTP 403). Over the 8 hours monitored after deployment, 138 of 212 ReceivePhoneNumber requests (65%) and 43 of 56 ReceiveEmail requests (77%) returned 403.
Additionally, a SourceTableTemporary inconsistency between the phone page (true) and email page (false) was identified. The email page's false setting caused BC to insert the CustomerEmailLog record as part of the same transaction as the Customer creation, widening the transaction scope and contributing to lock contention (two HTTP 409 lock timeout errors observed at 16:42 UTC).
Summary of Changes
| # | Severity | Category | Description |
|---|---|---|---|
| 1 | Critical | Fixed | Add inline permissions for all standard BC tables accessed by Insert(true) and ApplyCustomerTemplate on Pages 80000 and 80003 |
| 2 | Medium | Fixed | Set SourceTableTemporary = true on Page 80003 (ReceiveEmail) to match Page 80000 and narrow transaction scope |
Detailed Changes
1. Add Inline Permissions for Customer Creation Flow (Critical)
Files:
src/Page/Pag-80000.ReceivePhoneNumber.alsrc/Page/Pag-80003.ReceiveEmail.al
Before:
Permissions = tabledata CustomerPhoneLog = RIM,
tabledata Customer = RIM,
tabledata "NextivaConfig" = RIM;
After:
Permissions = tabledata CustomerPhoneLog = RIM,
tabledata Customer = RIM,
tabledata "NextivaConfig" = RIM,
tabledata "Customer Templ." = R,
tabledata "No. Series" = R,
tabledata "No. Series Line" = RM,
tabledata "Sales & Receivables Setup" = R,
tabledata "Marketing Setup" = R,
tabledata Contact = RIM,
tabledata "Contact Business Relation" = RIM,
tabledata "Business Relation" = R,
tabledata "Default Dimension" = RIM,
tabledata "Dimension Value" = R,
tabledata "Customer Posting Group" = R,
tabledata "Gen. Business Posting Group" = R,
tabledata "VAT Business Posting Group" = R;
Rationale: The inline Permissions property on a page object elevates the executing user's permissions for those specific tables during the page's execution context. This is the correct mechanism for API pages that run as a service account — the page grants itself the permissions it needs to execute, regardless of the user's assigned permission sets.
The BCDialerPermissions permission set (60000) cannot reference base application tables because the extension declares no dependencies ("dependencies": [] in app.json). The AL compiler only resolves tabledata references in permission sets against the extension's own symbols and declared dependencies. The inline Permissions property on pages does not have this restriction — it resolves at runtime against the full application.
Why these specific tables and permissions:
| Table | Permission | Accessed By |
|---|---|---|
| Customer Templ. | R | TemplateHeader.Get('ECOMMERCE') in OnInsertRecord |
| No. Series | R | InitCustomerNo reads the series configuration |
| No. Series Line | RM | InitCustomerNo reads and modifies (increments) the last used number |
| Sales & Receivables Setup | R | Customer OnInsert trigger reads default settings |
| Marketing Setup | R | OnInsert reads this to get business relation code for Contact creation |
| Contact | RIM | OnInsert creates a Contact record automatically from the Customer |
| Contact Business Relation | RIM | OnInsert creates the link between Customer and Contact |
| Business Relation | R | Looked up during Contact creation |
| Default Dimension | RIM | ApplyCustomerTemplate creates default dimension records |
| Dimension Value | R | Template dimension validation |
| Customer Posting Group | R | Template field validation during ApplyCustomerTemplate |
| Gen. Business Posting Group | R | Template field validation during ApplyCustomerTemplate |
| VAT Business Posting Group | R | Template field validation during ApplyCustomerTemplate |
2. Align SourceTableTemporary on Page 80003 (Medium)
File: src/Page/Pag-80003.ReceiveEmail.al
Before:
SourceTableTemporary = false;
After:
SourceTableTemporary = true;
Rationale: Page 80000 (ReceivePhoneNumber) uses SourceTableTemporary = true, meaning the CustomerPhoneLog record is inserted into a temporary in-memory table first, and the real database insert happens in the OnInsertRecord trigger logic. Page 80003 (ReceiveEmail) had SourceTableTemporary = false, causing BC to insert the CustomerEmailLog record into the real database table as part of the API page's transaction — before and alongside the Customer creation logic.
This inconsistency meant the email page held a wider transaction scope than the phone page: the CustomerEmailLog insert and the Customer creation were in the same transaction, holding locks on both tables simultaneously. Two HTTP 409 (SQL lock timeout) errors were observed at 16:42 UTC, both with exactly 30.5 seconds execution time (the BC lock timeout threshold), one on each page — consistent with the email page's wider lock window creating contention.
Setting SourceTableTemporary = true narrows the transaction to match the phone page's behavior. The OnInsertRecord trigger already handles the real CustomerEmailLog insert manually (via Rec.Insert() at the end of the trigger), so this change is transparent to the API's external behavior.
Object Inventory
| Object ID | Object Type | Name | Status |
|---|---|---|---|
| 80000 | Page | ReceivePhoneNumber | Modified — expanded inline Permissions |
| 80003 | Page | ReceiveEmail | Modified — expanded inline Permissions, SourceTableTemporary changed to true |
| — | app.json | Extension manifest | Modified — version bump to 2.2.1.0 |
Telemetry Evidence
The following data was collected from Application Insights for Production over the 8 hours following the v2.2.0.0 deployment (11:25–19:25 UTC, March 5, 2026):
HTTP Status Distribution
| Page | 201 (Success) | 403 (Permission Denied) | 409 (Lock Timeout) | Failure Rate |
|---|---|---|---|---|
| 80000 (ReceivePhoneNumber) | 74 | 138 | 1 | 65% |
| 80003 (ReceiveEmail) | 13 | 43 | 1 | 77% |
| 80001 (EndCall) | 112 | 0 | 0 | 0% |
| 80004 (EndMailCall) | 21 | 0 | 0 | 0% |
EndCall and EndMailCall pages are unaffected because they do not create Customer records — they only insert log records and perform a read-only Customer lookup.
403 Error Pattern
All 181 HTTP 403 errors have failureReason: Microsoft.Dynamics.Nav.Types.NavPermissionException. The errors are evenly distributed across the monitoring period — not a temporary spike, confirming this is a systematic permission gap rather than a transient issue.
409 Error Pattern
Both 409 errors occurred within 3 seconds of each other at 16:42 UTC:
- Page 80000: 30.50s execution,
NavCSideSQLLockTimeoutException - Page 80003: 30.52s execution,
NavCSideSQLLockTimeoutException
The simultaneous timing suggests a phone call and email arrived concurrently, and one blocked the other on the No. Series Line row lock during InitCustomerNo.
CU-60003 Job Queue Performance
13 consecutive runs exceeded the time threshold (RT0018), ranging from 12 seconds to 3 minutes 47 seconds. Execution times trended upward through the day, correlating with increasing SQL executes (38 to 532) and rows read (646 to 1,440). This is consistent with a growing backlog of pending records — expected behavior when 65-77% of incoming API calls fail before writing complete log records.
Known Limitations
No. Series Line Contention (Accepted)
InitCustomerNo modifies the No. Series Line record to increment the last used customer number. If two API calls (e.g., one phone, one email) arrive simultaneously and both need to create a new Customer, they will serialize on this single-row lock. The second call will wait up to 30 seconds (BC's SQL lock timeout) before failing with a 409. Over the 8-hour monitoring window, this occurred twice out of 270 total requests (0.7%) — an acceptable rate. The alternative (pre-assigning numbers or custom sequencing) adds significant complexity.
403 Failures May Have Created Incomplete Records
API calls that succeeded (201) before the 403 fix created Customer records correctly. However, the 138+43 = 181 failed calls may have left partial state — for example, a CustomerPhoneLog or CustomerEmailLog record was created (in the email page's case, since SourceTableTemporary was false), but the Customer creation failed. After deploying v2.2.1.0, review the log tables for records with blank Customer No. that were created during the v2.2.0.0 deployment window (March 4–5, 2026).
Deployment Notes
Pre-Deployment
- No data migration required
- No manual configuration changes needed
- No new dependencies
Deployment
- Build the
.apppackage via AL: Package in VS Code - Deploy to the target environment via Extension Management or the admin center
- The extension upgrade is transparent — no upgrade codeunit is needed
Post-Deployment Verification
- Monitor Application Insights for the first hour after deployment
- Verify HTTP 403 rate drops to 0% on Pages 80000 and 80003
- Verify incoming calls and emails create Customer records with
Name = 'New Customer'and correct Phone No./E-Mail - Check CU-60003 Job Queue execution times — they should decrease as the backlog clears
- Monitor for any new error-level (severity 3+) telemetry
Post-Deployment Cleanup
Review CustomerPhoneLog and CustomerEmailLog tables for records created during the v2.2.0.0 window (March 4–5) with blank Customer No. — these may represent calls/emails that arrived during the 403 period and need manual reconciliation.
Testing
| # | Scenario | Expected Result |
|---|---|---|
| 1 | Incoming call with unknown phone number | New Customer created successfully (HTTP 201). No 403 error. Customer has Name = 'New Customer', correct Phone No., ECOMMERCE template defaults, and an associated Contact record. |
| 2 | Incoming email with unknown email address | Same as #1 but with E-Mail field. SourceTableTemporary = true means the CustomerEmailLog insert is handled in the trigger, not by BC's automatic page insert. |
| 3 | Simultaneous phone call and email for new customers | Both succeed (HTTP 201). If they arrive within milliseconds, one may get a 409 (No. Series contention) — this is the accepted edge case. |
| 4 | Incoming call with known phone number | Existing Customer found, no new Customer created. HTTP 201 with Customer No. populated. |
| 5 | EndCall for known phone number | HTTP 201, Customer No. populated on the log record. No permission errors. |
| 6 | Edit Customer Card during incoming call | Save completes without "out of date" error (validates LockTable removal from v2.2.0.0 is still effective). |