Alarm billing automation ties your contract roster and service terms to a scheduler that generates recurring monthly revenue invoices, prorates mid-cycle changes, runs autopay, and syncs cleanly into accounting. It is built for monitoring and guard companies that must bill RMR on time and reconcile payments without double entries. This guide shows how we implement it and what to watch for.
RMR billing automation for alarm companies: a predictable job that becomes reliable once you model contracts, compute cycle math deterministically, and make the accounting sink idempotent. This is the same pattern whether you use SedonaOffice, another industry system, or a spreadsheet as the source of truth.
The problem it solves
Most alarm companies start RMR on spreadsheets or in a vertical platform and hand off invoicing to a bookkeeper once a month. The manual flow means missed proration, late invoices, and re-keyed data between systems. If you scale monitoring or patrol SKUs, the failure pattern is the same: recurring changes do not land on the right bill and the ledger drifts.
| Process | Manual billing | Automated alarm RMR billing |
|---|---|---|
| Contract changes | Someone edits a spreadsheet and hopes billing sees it | Contract DB is the source, scheduler computes proration on change date |
| Invoice creation | Batch export, copy fields, import to accounting | Engine generates invoices with line math and unique keys |
| Autopay | Run cards by hand, retry later if declined | On-file ACH or card with safe retries and dunning sequence |
| Reconciliation | Mark paid manually and re-key deposits | Payments post back to invoices and ledger updates automatically |
| Errors | Duplicates, missed cycles, wrong start dates | Idempotent keys prevent dupes, logs catch exceptions fast |
Definition: Alarm company RMR billing is the monthly cycle that charges recurring fees for monitoring, service plans, and patrol, including proration for mid-cycle activations, upgrades, and cancels.
According to Atradius' Payment Practices Barometer, roughly half of B2B invoices are paid late in many markets. Tight, automated invoicing and dunning materially reduces days sales outstanding by removing manual lag (source: https://atradius.us/reports/).
How the automation works
The architecture is simple on paper and unforgiving in the details. A contracts table is the source of truth. A scheduler computes which contracts bill this cycle and the exact amount. An invoicing engine creates invoices and pushes them into your accounting system. A payment worker runs autopay and retries safely. A reconciliation worker posts payments and closes the loop.
- Contracts and terms: One row per active contract. Fields cover site, plan, start and end dates, price, tax flags, billing day, and autopay token reference.
- Billing scheduler: Runs daily. Emits one invoice per contract when the cycle hits, including proration math for starts, upgrades, downgrades, and cancels.
- Invoicing engine: Generates invoice numbers, line descriptions, taxes, and unique idempotency keys so accounting never gets a duplicate.
- Accounting sink: Writes invoices to your general ledger system. If your vertical platform cannot push directly, bridge with a stable CSV mapping or a service connector.
- Payments and dunning: Charges stored ACH or cards on the due date, retries on a backoff ladder, then emails dunning with a pay link when required.
Step-by-step: how to build it
1) Model your contracts and service terms
Put the contract in a single table that everything else reads. Keep it boring and explicit.
-- contracts
create table contracts (
contract_id uuid primary key,
customer_id uuid not null,
site_address text not null,
plan_code text not null, -- e.g., MON_BASIC, PATROL_PLUS
price_monthly_cents integer not null,
tax_rate_basis numeric(6,4) not null default 0.0000,
bill_day_of_month int not null check (bill_day_of_month between 1 and 28),
start_date date not null,
end_date date,
status text not null check (status in ('active','suspended','canceled')),
autopay_token_ref text, -- reference into your payment vault
updated_at timestamptz not null default now()
);Key gotcha: do not store implicit rules. If the plan has an included patrol, put that into a plan catalog table and join it for descriptions, not in code.
2) Compute billing windows and proration deterministically
Proration is where manual billing drifts. Compute it with dates, not with guesswork.
-- invoice candidates for a given run_date
with due as (
select c.contract_id,
greatest(c.start_date, date_trunc('month', :run_date)::date) as period_start,
(date_trunc('month', :run_date) + interval '1 month - 1 day')::date as period_end,
c.bill_day_of_month,
c.price_monthly_cents,
c.tax_rate_basis
from contracts c
where c.status = 'active'
and (c.end_date is null or c.end_date >= date_trunc('month', :run_date))
)
select contract_id,
period_start,
period_end,
/* bill on the configured day, clamp to last day if month shorter */
least(make_date(extract(year from :run_date)::int,
extract(month from :run_date)::int,
bill_day_of_month),
period_end) as bill_date,
case
when period_start = date_trunc('month', :run_date)::date
then price_monthly_cents
else -- prorate on days active this month
round(price_monthly_cents * (1.0 * (period_end - period_start + 1)) /
(1.0 * (period_end - date_trunc('month', :run_date)::date + 1)))::int
end as amount_cents,
tax_rate_basis
from due;Key gotcha: clamp bill_day_of_month to avoid missing February. Do not let a 31st roll into the next month silently.
3) Generate invoice objects with clear lines and unique keys
Build the invoice in code with human-readable lines. Attach an idempotency key that ties back to contract and month.
// build-invoice.js
import crypto from "node:crypto";
export function buildInvoice({ contract, periodStart, periodEnd, amountCents }) {
const isoMonth = periodStart.toISOString().slice(0,7); // yyyy-mm
const idem = `inv:${contract.contract_id}:${isoMonth}`;
const idemHash = crypto.createHash('sha256').update(idem).digest('hex');
return {
externalId: idemHash, // used to prevent duplicates in the sink
customerRef: contract.customer_id,
issueDate: new Date(),
dueDate: new Date(periodStart.getFullYear(), periodStart.getMonth(), contract.bill_day_of_month),
currency: "USD",
lines: [{
sku: contract.plan_code,
description: `${contract.plan_code} monitoring ${isoMonth} (${periodStart.toISOString().slice(0,10)}, ${periodEnd.toISOString().slice(0,10)})`,
qty: 1,
unitPriceCents: amountCents,
taxRate: contract.tax_rate_basis
}],
memo: `RMR ${isoMonth} ${contract.site_address}`
};
}Key gotcha: the idempotency key must be stable across retries. Never derive it from an auto-incrementing invoice number in accounting.
4) Prevent duplicates with a tiny ledger
Store a write ledger that records every attempted sink write and enforces uniqueness.
create table invoice_ledger (
external_id text primary key, -- idem hash
contract_id uuid not null,
period_ym char(7) not null, -- yyyy-mm
sink_status text not null check (sink_status in ('pending','ok','error')),
sink_ref text,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);// write-to-sink.js
export async function writeInvoiceToAccounting(sink, invoice, db) {
const exists = await db.select('external_id').from('invoice_ledger').where({ external_id: invoice.externalId }).first();
if (exists) return { status: 'duplicate', ref: exists.external_id };
await db('invoice_ledger').insert({
external_id: invoice.externalId,
contract_id: invoice.customerRef,
period_ym: invoice.lines[0].description.split(' ')[1],
sink_status: 'pending'
});
try {
const ref = await sink.createInvoice(invoice); // your adapter handles CSV/API/RPA
await db('invoice_ledger').where({ external_id: invoice.externalId }).update({ sink_status: 'ok', sink_ref: ref });
return { status: 'ok', ref };
} catch (e) {
await db('invoice_ledger').where({ external_id: invoice.externalId }).update({ sink_status: 'error' });
throw e;
}
}Key gotcha: record both success and failure. A noisy failure log is how you avoid silent missed invoices.
5) Choose your accounting sink adapter
You have three safe patterns. Pick the one your stack allows.
- Direct API: when your accounting tool exposes a supported programmatic write. Keep retries and 429 handling in your adapter.
- CSV handoff: write a stable CSV with headers your accounting import expects. Version the mapping and archive every file.
- Assisted UI automation: last resort. Use it only for stable, low-variance forms and wrap it in smoke tests.
Example CSV mapping for a simple one-line invoice import:
Customer,Invoice Date,Due Date,Item,Description,Qty,Rate,Amount,Tax Rate,Reference
ABC Security,2026-09-01,2026-09-01,MON_BASIC,Monitoring 2026-09 (2026-09-01, 2026-09-30),1,29.00,29.00,0.0000,inv:3a12...Key gotcha: do not reuse invoice numbers from your engine. Let accounting own official numbering while you enforce uniqueness with externalId.
6) Autopay and dunning without chargeback surprises
Run on the due date. Retry on a fixed ladder and keep the tone factual.
# autopay-retry.yml
attempts:
- t_plus_days: 0 # day due
channel: autopay
- t_plus_days: 3
channel: autopay
- t_plus_days: 7
channel: email_dunning
- t_plus_days: 14
channel: email_dunning
- t_plus_days: 21
channel: suspend_flag # mark for ops reviewKey gotcha: never store raw payment data in your engine. Keep only a token reference and the last 4 for receipts. Use your payment provider's vault.
7) Reconcile payments and close the loop
Post successful payments to the accounting sink and mark invoices paid with the sink's native reference. Keep a simple receipt for every charge.
create table payment_receipts (
payment_id uuid primary key,
external_id text not null, -- tie back to invoice externalId
sink_ref text not null, -- accounting payment id/number
amount_cents integer not null,
method text not null, -- ach, card
last4 text,
created_at timestamptz not null default now()
);Key gotcha: do not mutate historical invoice lines to "fix" accounting. Issue credits or adjustments so your audit trail survives.
Where it gets complicated
No direct integration from your vertical platform. Many alarm and guard platforms gate integrations behind paid modules or partner programs. Bridge the gap with an export-driven adapter or a lightweight connector rather than hard-coding to a proprietary UI.
QuickBooks Desktop vs Online and multicurrency. Accounting variants differ. Treat the sink as swappable and keep currency handling explicit. Multicurrency settings are often irreversible once enabled, so confirm before importing foreign-currency invoices.
Proration edges. Mid-month starts, mid-cycle upgrades, and early cancels stack. Compute on calendar days for the period and round once at the end. Put the date math in one function and unit-test it.
Tax jurisdiction rules. Some sites are taxable, others are not. Keep a per-site tax basis and rate in the contract record or a join table. Never deduce taxes from zip codes on the fly.
Invoice numbering and duplicates. Accounting owns numbers. Your engine owns idempotency. If an import is retried, the sink must detect your externalId and update-or-ignore, not create a second invoice.
Payments, disputes, and suspension flags. Late autopay is not just a dunning email. Flag accounts for suspension in your operations UI after the last retry and make that flag visible to dispatch or patrol scheduling if your policy requires it.
What this actually changes
For alarm monitoring and guard operators, this pattern turns billing into a predictable engine: proration handled, invoices on time, payments applied, and accounting clean. The value is structural: fewer manual touches, fewer missed cycles, and a shorter path from service to cash.
Late payments are a reality across B2B. Atradius reports high shares of overdue invoices in its Payment Practices Barometer, which is why clean invoicing and systematic retries matter (source: https://atradius.us/reports/). An automation that issues correct invoices on schedule and launches dunning consistently is one of the few levers you fully control.
Frequently asked questions
What is the right billing software for alarm companies?
There is no single right tool. The reliable pattern is contracts as the source of truth, a scheduler that computes cycle and proration, and a sink adapter that writes invoices into your accounting system. If your vertical platform cannot push invoices directly, bridge it with an export-driven connector or a CSV import that is versioned and logged.
Can we automate alarm company RMR billing without an API?
Yes. Use a stable export as the contract source and a CSV import for invoices, with a small ledger to enforce idempotency. Where a vendor exposes programmatic access, swap the CSV sink for a connector. The engine stays the same: contract math, invoice objects with unique keys, and a reconciliation loop.
How do you handle proration for mid-cycle activations and upgrades?
Compute on calendar days for the billing month. Charge the monthly price multiplied by active days divided by total days in the month, rounded once. The same approach works for upgrades and downgrades: compute the delta line from the date of change through month end and include it on the next bill.
Does this work with QuickBooks Desktop or Online?
Both are viable via different adapters. Treat your accounting system as a pluggable sink. If your environment prefers file-based imports, write a stable CSV with documented headers. If it supports programmatic writes, use a connector with retries and idempotency. Keep numbering under accounting control.
Can we charge cards or ACH automatically on the due date?
Yes, with a payment provider that supports stored tokens. Keep only a token reference and the last 4 digits in your contracts table. Run autopay on the due date with a short retry ladder, then move to email dunning with a pay link. Never store raw payment data in your engine.
What does this cost monthly to run?
The runtime cost is modest: a database, a scheduler, and your chosen payment and accounting platforms. The primary investment is the initial build and integration work. Ongoing costs are usually tied to payment processing fees and any vendor modules you choose to enable for exports or programmatic access.
If you want a deeper dive into accounting adapters, see our related post on integrating industry platforms with accounting, including options for moving invoices into QuickBooks cleanly: /blog/sedonaoffice-api-integration-to-quickbooks. For help scoping your billing engine or replacing brittle manual steps, see our automation services at /services#workflow-automation and book a working session at /book.
Want us to build this for you?
Nine questions, about 90 seconds. You see the hours it is costing you, then pick a time. No pitch.
Get your free assessment