CPA advisory for owner-led businesses

Accounting and tax guidance that keeps the next decision clear.

Bennison CPA and Consultants helps small businesses, professionals, and growing teams make sense of the numbers, clean up financial operations, and plan the next move with confidence.

15+ years across audit, assurance, consulting, and client accounting.

CPA-led work with direct access to the person doing the thinking.

Practical deliverables built for decisions, not just compliance folders.

Services

Focused help for the places financial work gets messy.

The firm is built for thoughtful, right-sized engagements: enough structure to be dependable, without burying you in layers, handoffs, or generic checklists.

01

Accounting systems and clean books

QuickBooks Online setup, cleanup, chart of accounts design, reconciliation support, and month-end workflows that make your numbers easier to trust.

02

Tax planning and preparation

Individual and small-business tax support for clients who want tax work connected to the full financial picture, not handled as a last-minute filing exercise.

03

Advisory and project work

Help with cash flow questions, finance process improvements, management reporting, and decision support when you need a CPA perspective on a specific business issue.

04

Transaction and diligence support

Project-based support for business transitions, quality-of-earnings style review, financial statement analysis, and preparation for lender, buyer, or stakeholder requests.

Industry focus

Support for businesses where the accounting has real operational texture.

Craig’s background includes assurance leadership, due diligence, consulting, and finance leadership across owner-led businesses and organizations with project, inventory, labor, revenue recognition, and reporting complexity.

Construction

Project-based accounting, job cost visibility, WIP considerations, contractor reporting, and cleaner systems for owners who need to understand margin by job.

Manufacturing

Inventory, production costs, margin analysis, internal controls, and reporting support for teams that need better numbers behind pricing and operating decisions.

Service organizations

Professional services, SaaS and technology, nonprofit, and recurring-service models where revenue recognition, cash flow, clean bookkeeping, timely reporting, and practical advisory support matter.

Bio

Meet Craig Bennison, CPA

Craig Bennison, CPA

Craig Bennison is a CPA licensed in Florida and Ohio with experience across assurance, consulting, client accounting, due diligence, and hands-on finance leadership. Before starting Bennison CPA and Consultants, Craig served in assurance leadership roles with HBK and RSM, working with manufacturing, construction, SaaS and technology, distribution, nonprofit, college and university, and service-organization clients.

Craig also spent three years as CFO of a technology company, giving him a practical operator’s view of the relationship between finance, systems, reporting, and business decisions. He started the firm to give business owners direct help from someone who can see both the accounting details and the bigger business question behind them.

He earned both a Master of Science in Accounting and a Bachelor of Business Administration from Kent State University. The firm works especially well for clients who value clear explanations, clean follow-through, and advisory work that translates into next steps.

How it works

A straightforward first conversation.

  1. 1

    Share the situation

    Send a short note about your business, your deadline, and the question you are trying to answer.

  2. 2

    Scope the right help

    Craig will identify whether this is a quick advisory call, a tax engagement, cleanup work, or a project.

  3. 3

    Move with clarity

    You will know the next step, what information is needed, and what the engagement is intended to solve.

FAQs

Common questions before reaching out.

Do you work with clients outside Central Florida?

Yes. Bennison CPA is based in Central Florida and can support many engagements virtually across the United States.

What type of client is the best fit?

Owner-led businesses, professionals, and small teams that want accounting, tax, or advisory work tied to practical business decisions.

Can you help clean up QuickBooks Online?

Yes. Cleanup, reconciliation, chart of accounts refinement, and process setup are all strong fits for the firm.

Do you handle tax-only work?

Selective tax engagements are available, especially when the tax work connects to broader planning, bookkeeping, or advisory needs.

What happens after I send the form?

Your message opens in your email app so you can review and send it directly to Craig. He can then follow up with next steps.

Contact

Send the context. Craig will help sort the next step.

If we just met, include where we connected and the financial question on your mind. A few sentences are enough to start.

Your message will be sent securely through Formspree.

(function () { if (window === window.top) return; const allowedParentOrigins = ["https://www.perplexity.ai","https://perplexity.ai","https://testing.perplexity.ai","https://staging.perplexity.ai","https://*.preview.i.perplexity.ai","http://localhost:3000","http://127.0.0.1:3000","http://localhost:5173","http://127.0.0.1:5173"]; const MAX_FONT_BYTES = 500 * 1024; const MAX_TOTAL_FONT_BYTES = 2 * 1024 * 1024; let scrollForwarding = false; let scrollRaf = 0; let trustedTopOrigin = null; // Allow entries like "https://*.preview.i.perplexity.ai" — the wildcard // matches a single DNS label (no dots), so "https://*.foo" cannot stretch // across multiple labels. function matchesAllowedOrigin(origin) { if (!origin) return false; for (const entry of allowedParentOrigins) { if (!entry.includes("*")) { if (entry === origin) return true; continue; } const pattern = new RegExp( "^" + entry.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^.]+") + "$", ); if (pattern.test(origin)) return true; } return false; } // Trust decision: when the sender is same-origin-visible (event.origin is a // real origin like https://www.perplexity.ai) we trust event.origin directly. // When event.origin is "null" (opaque broker srcdoc), we fall back to the // broker's stamped `parentOrigin` to identify the top window. The fallback // is claim-only — we rely on the browser's native `targetOrigin` enforcement // on the response path (see postToTrustedTop) to ensure replies can't be // delivered to anyone but the actual top window of that claimed origin. function getTrustedParentOrigin(event) { const forwardedParentOrigin = typeof event.data.parentOrigin === "string" ? event.data.parentOrigin : null; const parentOrigin = event.origin === "null" ? forwardedParentOrigin : event.origin; return matchesAllowedOrigin(parentOrigin) ? parentOrigin : null; } // All responses go to window.top with targetOrigin = the allowlisted origin. // An attacker that iframes us inside their own null-origin broker can claim // any parentOrigin they like, but the browser will drop the reply whenever // the real top's origin doesn't match — so the screenshot never leaves. function postToTrustedTop(message) { if (!trustedTopOrigin) return; try { window.top.postMessage(message, trustedTopOrigin); } catch (_error) {} } function inlineAll(original, clone) { if (original.nodeType !== 1 || clone.nodeType !== 1) return; try { const computedStyle = getComputedStyle(original); // cssText on a computed style is the serialized declaration in modern // Chromium/Safari — a single read beats enumerating ~400 longhand // properties. Firefox returns "" here, so we fall back on empty. const serialized = computedStyle.cssText; if (serialized) { clone.style.cssText = serialized; } else { const parts = new Array(computedStyle.length); for (let index = 0; index < computedStyle.length; index += 1) { const property = computedStyle[index]; parts[index] = `${property}:${computedStyle.getPropertyValue(property)};`; } clone.style.cssText = parts.join(""); } } catch (_error) {} const originalChildren = original.children; const clonedChildren = clone.children; for ( let index = 0; index < originalChildren.length && index < clonedChildren.length; index += 1 ) { inlineAll(originalChildren[index], clonedChildren[index]); } } function extractFontUrl(srcValue) { const matches = [ ...srcValue.matchAll( /url\(["']?([^"')]+)["']?\)(?:\s*format\(["']?([^"')]+)["']?\))?/gi, ), ]; if (matches.length === 0) return null; const woff2 = matches.find((m) => m[2] && m[2].toLowerCase().includes("woff2")); if (woff2) return woff2[1]; const woff = matches.find((m) => m[2] && m[2].toLowerCase().includes("woff")); if (woff) return woff[1]; return matches[0][1]; } // Cache resolved font URL -> data URI across captures. Fonts on a page // essentially never change, and a batch run emits multiple captures back to // back — without this we'd refetch + re-base64 every time. const fontDataUriCache = new Map(); const SRC_DECLARATION_RE = /src\s*:\s*[^;}]+/i; async function fetchAsDataUri(url) { if (fontDataUriCache.has(url)) return fontDataUriCache.get(url); let dataUri = null; try { const response = await fetch(url, { mode: "cors", credentials: "omit" }); if (response.ok) { const blob = await response.blob(); if (blob.size <= MAX_FONT_BYTES) { dataUri = await new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(typeof reader.result === "string" ? reader.result : null); reader.onerror = () => resolve(null); reader.readAsDataURL(blob); }); } } } catch (_error) { dataUri = null; } fontDataUriCache.set(url, dataUri); return dataUri; } function collectFontFaceRuleTexts() { const rules = []; for (const sheet of document.styleSheets) { let cssRules; try { cssRules = sheet.cssRules; } catch (_error) { continue; } if (!cssRules) continue; for (const rule of cssRules) { const cssText = rule.cssText || ""; if (cssText.startsWith("@font-face")) rules.push(cssText); } } return rules; } async function buildInlinedFontCss() { const ruleTexts = collectFontFaceRuleTexts(); if (ruleTexts.length === 0) return null; const resolved = ruleTexts.map((cssText) => { if (!SRC_DECLARATION_RE.test(cssText)) return null; const srcMatch = cssText.match(/src\s*:\s*([^;}]+)[;}]/i); if (!srcMatch) return null; const url = extractFontUrl(srcMatch[1]); if (!url) return null; try { return { cssText, url: new URL(url, document.baseURI).href }; } catch (_error) { return null; } }); const dataUris = await Promise.all( resolved.map((entry) => (entry ? fetchAsDataUri(entry.url) : Promise.resolve(null))), ); const inlined = []; let totalBytes = 0; for (let index = 0; index < resolved.length; index += 1) { const entry = resolved[index]; const dataUri = dataUris[index]; if (!entry || !dataUri) continue; const approxBytes = dataUri.length * 0.75; if (totalBytes + approxBytes > MAX_TOTAL_FONT_BYTES) break; totalBytes += approxBytes; inlined.push(entry.cssText.replace(SRC_DECLARATION_RE, `src: url("${dataUri}")`)); } return inlined.length > 0 ? inlined.join("\n") : null; } function stripExternal(clone) { const images = clone.querySelectorAll("img"); for (let index = 0; index < images.length; index += 1) { const src = images[index].getAttribute("src"); if (src && !src.startsWith("data:")) images[index].removeAttribute("src"); } const elements = clone.querySelectorAll("*"); for (let index = 0; index < elements.length; index += 1) { const style = elements[index].style.cssText; if (style && style.includes("url(")) { elements[index].style.cssText = style.replace( /url\(["']?(?!data:)[^)"']*["']?\)/gi, "none", ); } } } function emitScroll() { scrollRaf = 0; if (!scrollForwarding) return; postToTrustedTop({ type: "INLINE_EDIT_SCROLL", scrollX: window.scrollX, scrollY: window.scrollY, }); } window.addEventListener( "scroll", function () { if (!scrollForwarding || scrollRaf) return; scrollRaf = requestAnimationFrame(emitScroll); }, { passive: true, capture: true }, ); async function handleCaptureRequest(event) { const requestId = event.data.requestId; const scrollX = window.scrollX; const scrollY = window.scrollY; const width = window.innerWidth; const height = window.innerHeight; function postResult(dataUrl) { postToTrustedTop({ type: "INLINE_EDIT_SCREENSHOT_RESULT", requestId, dataUrl, scrollX, scrollY, }); } try { // Wait for any pending web fonts to resolve so both inline metrics and // the @font-face inlining below see the same loaded faces. if (document.fonts && document.fonts.ready) { try { await document.fonts.ready; } catch (_error) {} } const clone = document.documentElement.cloneNode(true); inlineAll(document.documentElement, clone); const removedNodes = clone.querySelectorAll("script,link[rel=\"stylesheet\"],style"); for (let index = 0; index < removedNodes.length; index += 1) { removedNodes[index].remove(); } stripExternal(clone); // Re-embed web fonts as data-URI @font-face rules so the SVG rasterizer // can resolve them — external font URLs aren't fetched during // foreignObject rendering, which would otherwise force a fallback face // and change text metrics. const inlinedFontCss = await buildInlinedFontCss(); if (inlinedFontCss) { const styleEl = document.createElement("style"); styleEl.textContent = inlinedFontCss; const head = clone.querySelector("head"); if (head) head.appendChild(styleEl); else clone.insertBefore(styleEl, clone.firstChild); } const html = new XMLSerializer().serializeToString(clone); const svg = `` + '' + `
` + `
` + html + "
"; const svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; const image = new Image(); image.onload = function () { const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; canvas.getContext("2d").drawImage(image, 0, 0); postResult(canvas.toDataURL("image/png")); }; image.onerror = function () { postResult(null); }; image.src = svgUrl; } catch (_error) { postResult(null); } } window.addEventListener("message", function (event) { if (!event.data) return; // Only accept messages from the direct parent frame. Blocks sibling / // unrelated-window postMessage senders that could otherwise reach us. if (event.source !== window.parent) return; const trustedParentOrigin = getTrustedParentOrigin(event); if (!trustedParentOrigin) return; trustedTopOrigin = trustedParentOrigin; if (event.data.type === "INLINE_EDIT_SCROLL_START") { scrollForwarding = true; emitScroll(); return; } if (event.data.type === "INLINE_EDIT_SCROLL_STOP") { scrollForwarding = false; if (scrollRaf) cancelAnimationFrame(scrollRaf); scrollRaf = 0; return; } if (event.data.type !== "INLINE_EDIT_CAPTURE_REQUEST") return; handleCaptureRequest(event); }); })();