Knox for Craft CMS

Installation

Requirements

  • Craft CMS 5.3 or later
  • Craft Commerce 5.0 or later
  • PHP 8.2 or later
  • A Fortnox account with an API licence

That last one is not the same as a Fortnox subscription. If the account cannot use the API, Fortnox answers every call with error 2001103, and Knox reports it in those words rather than as a generic failure.

Install

composer require justinholtweb/craft-knox
php craft plugin/install knox

Or find Knox in the Craft Plugin Store and install it from there.

Register an integration

Knox talks to Fortnox as your integration, not as a shared one, so the credentials are yours.

  1. Go to apps.fortnox.se/developer and create an integration.
  2. Note its Client ID and Client Secret.
  3. Put them in your .env:

    FORTNOX_CLIENT_ID="your-client-id"
    FORTNOX_CLIENT_SECRET="your-client-secret"
    
  4. In Craft, open Settings → Plugins → Knox and set the two fields to $FORTNOX_CLIENT_ID and $FORTNOX_CLIENT_SECRET. Save.

Register the redirect URI

Knox shows you a Redirect URI on the settings screen. Copy it into your Fortnox integration exactly, character for character — Fortnox compares it as a string, and a trailing slash is a different URI.

If your site sits behind a proxy or answers on a different public host than Craft thinks it does, set Redirect URI override instead of fighting the environment.

Choose your scopes, then connect

This is the step worth slowing down for.

Fortnox freezes the scope set at the moment you approve the integration. Adding a scope to Knox's settings afterwards does nothing at all for an account that is already connected — you have to reconnect. Knox reads back what was actually granted and warns you on the settings screen when the two disagree, but it is easier to get right the first time.

Knox asks for invoice, customer and companyinformation always. Add settings, payment and bookkeeping unless you know you will not want payments or booked invoices.

Then press Connect to Fortnox and approve.

Two checks before your first order

Both are buttons on the settings screen, and both take a second.

Detect from Fortnox, under Öresavrundning. Knox reads your company's recent invoices and works out whether öresutjämning is switched on. Read what it says — it is the single most common reason a Fortnox integration's invoices quietly stop matching the payments. See Öresavrundning.

Check these exist in Fortnox, under Accounts. An invoice row posted to an account that is not in your chart is rejected by Fortnox. Finding that out now beats finding it out on order 200.

Set a customer

Fortnox requires an existing customer on every invoice — there is no such thing as an invoice without one. So either:

  • set a collective customer number (the "Webbkund" most shops already have), or
  • turn on Sync customers, which gives each buyer their own record.

The choice is not only about tidiness. Fortnox stores the VAT type on the customer, so reverse-charged and export sales need real customer records. See VAT.

Rehearse

Before you let it loose on live orders:

php craft knox/sync/backfill --dry --limit=10

That prints what each uninvoiced order would be invoiced at, which VAT treatment Knox picked and why, and which orders it would refuse and for what reason. Nothing is sent.

What it costs

Knox is a one-off $99 with a $79/year renewal. One edition — everything below is in it.

Invoices from completed orders
Öresavrundning modelled, detected and explained
Reconciliation guard — refuses an invoice that disagrees with the order
Re-check the total Fortnox returns after every create
Never retries a create whose outcome is unknown
BAS account mapping, validated against your chart
Swedish VAT, EU reverse charge, OSS and export
Fortnox customers created and reused per buyer
Bookkeeping, with a financial-year pre-check
Register the checkout payment in Fortnox
Poll Fortnox for invoices paid there
Credit invoices on refund
Article matching by SKU
Customer-facing invoice downloads
Order panel, invoice list and PDF in the control panel
Connection log with request and response bodies
Console backfill, preview and reconcile
Trigger on an order status rather than completion

The renewal buys updates and support; the plugin keeps working if you let it lapse.