ml-connector
DATEVShopify

DATEV and Shopify integration

Shopify runs the online storefront and collects the orders, payments, and refunds. DATEV is the accounting backend the tax advisor uses to close the books. Connecting the two means each paid order and any refund flow into DATEV as bookings instead of being re-keyed from a manual export. ml-connector reads the sales records from Shopify, turns them into DATEV's strict file formats, and submits them as asynchronous jobs. Because DATEV's general ledger and chart of accounts are not readable through the API, the accounts stay defined in DATEV and ml-connector maps to them.

How DATEV works

DATEV is not a conventional REST API. The accounting engine, DATEV Rechnungswesen, is on-premise and accepts finalized bookings only as EXTF CSV file jobs submitted through a cloud relay, which makes them one-way and write-only. Booking suggestions and invoice data go to DATEV Unternehmen Online as DXSO XML jobs, and invoice PDFs upload through the documents REST API. A clients REST API lists the companies you can access, and its client id becomes the path parameter for every booking job. Authentication is OAuth 2.0 Authorization Code with PKCE against login.datev.de, with an interactive user login and no machine-to-machine flow, and access tokens last 15 minutes. There are no webhooks, so job status is read by polling.

How Shopify works

Shopify exposes orders, customers, transactions, refunds, products, and Shopify Payments payouts through its GraphQL Admin API, with the legacy REST Admin API still available but frozen for new features. Apps authenticate with OAuth 2.0 Authorization Code and send an X-Shopify-Access-Token header; an offline token does not expire, which suits a server-side sync. List queries page with Relay cursors at up to 250 records, and full historical exports use Bulk Operations. Shopify pushes events such as orders/paid and refund changes by webhook, signed with HMAC-SHA256, and deduped on the X-Shopify-Webhook-Id header. Shopify has no GL chart of accounts, no AP ledger, and no native vendor object, so the ledger lives in DATEV.

What moves between them

The flow runs one way, from Shopify into DATEV. ml-connector reads paid orders, their payment transactions, and refunds from Shopify, then writes them into DATEV as booking entries. Each paid order becomes an EXTF CSV booking batch posted to DATEV Rechnungswesen, or a DXSO XML booking suggestion sent to DATEV Unternehmen Online, with revenue and receivable accounts, the VAT tax code, and cost centers filled in, and refunds post as reversing bookings. Where an invoice document is generated, the PDF uploads to DATEV Unternehmen Online as an outgoing-invoice document. Cadence follows your booking schedule, typically daily or per close, since DATEV is built for periodic batch submission rather than high-frequency polling, with orders/paid and refund webhooks acting as the trigger. Nothing flows back from DATEV, because finalized bookings and the chart of accounts cannot be read through the API.

How ml-connector handles it

ml-connector stores both credential sets encrypted. On the Shopify side it completes the OAuth 2.0 Authorization Code flow, verifies the callback HMAC against the client secret, and uses an offline access token so the server-side sync keeps running without a merchant session, scoping every call to the one myshopify shop domain. The DATEV side is the harder bridge: its OAuth requires an interactive login with a code_challenge using S256, a state value of at least 20 characters, and a nonce, and the 15-minute access token is refreshed by sending the client_id only, never the client_secret. Each DATEV call carries the Bearer token plus the X-DATEV-Client-Id header, and the DATEV client id from the clients API becomes the path parameter for every booking job. Sales records are read from Shopify by paging GraphQL cursors at 250 per page, with Bulk Operations used for a large historical first load. Reading orders older than 60 days needs the read_all_orders scope, which Shopify gates behind Partner approval, so backfill depth depends on that grant. The records are then transformed into DATEV's exact formats: EXTF CSV must be UTF-8 with precomposed (NFC) characters or DATEV rejects it silently, and DXSO XML must validate against DATEV's XSD. GL accounts and document types matter here. DATEV does not return its chart of accounts, so the revenue, receivable, and tax accounts are mapped during setup, Shopify VAT rates from taxLines map to DATEV tax codes, and document types are client-specific, so ml-connector fetches the allowed types before any PDF upload rather than hardcoding them. Submission is asynchronous: ml-connector submits a job, receives a job id, and polls with exponential backoff and jitter starting near 5 seconds and capped near 60, because DATEV does not publish processing times. Idempotency is handled per channel. Shopify webhooks dedupe on the X-Shopify-Webhook-Id, and the Shopify order GID is carried as the external key so a re-read order upserts instead of double-booking. EXTF files dedupe on a stable filename plus document type, so ml-connector generates deterministic filenames to make retries safe and to avoid DATEV duplicate error DCO01253, and document uploads use the idempotent PUT with a supplied GUID. Webhook endpoints return 401 on a bad signature so Shopify keeps retrying. Every record carries a full audit trail and can be replayed if a downstream job fails.

A real-world example

A mid-sized German direct-to-consumer brand, roughly sixty staff, sells through a Shopify store and has its books kept by an outside tax advisor on DATEV. Before the integration, a bookkeeper exported the month's orders and refunds from Shopify, reworked them by hand into DATEV's booking layout, guessed at the right VAT codes, and the advisor chased mismatched tax and missing figures before every VAT filing. With DATEV and Shopify connected, each paid order posts into DATEV as a booking on the correct revenue and tax accounts, refunds post as reversing entries, and the advisor opens a period that already balances. The manual re-keying and the tax-code cleanup are gone.

What you can do

  • Post paid Shopify orders and refunds into DATEV as EXTF CSV or DXSO XML bookings.
  • Map Shopify order totals and taxLines to DATEV revenue, receivable, and tax accounts, since DATEV does not expose its chart of accounts.
  • Upload generated invoice PDFs into DATEV Unternehmen Online against the matching DATEV client and document type.
  • Bridge Shopify's offline OAuth token to DATEV's interactive OAuth login with PKCE and a 15-minute, auto-refreshed token.
  • Trigger on Shopify webhooks and submit booking jobs, with GID dedup, deterministic filenames, retries, and a full audit trail.

Questions

Which direction does data move between DATEV and Shopify?
Data moves one way, from Shopify into DATEV. Paid orders and refunds become DATEV bookings, and any generated invoice PDF uploads to DATEV Unternehmen Online. DATEV's finalized bookings and chart of accounts cannot be read through the API, so nothing flows back to the Shopify store.
How are DATEV bookings submitted, given there are no webhooks?
Bookings are submitted as asynchronous file jobs, EXTF CSV to DATEV Rechnungswesen or DXSO XML to DATEV Unternehmen Online. DATEV does not push status, so ml-connector submits a job, gets a job id, and polls with exponential backoff and jitter until the job reports complete or failed. EXTF files dedupe on filename plus document type, so ml-connector uses stable filenames to keep retries safe and avoid the DCO01253 duplicate error.
How does the integration map Shopify taxes and stay in sync without GL access?
Shopify carries VAT in each order's taxLines, and DATEV does not return its chart of accounts, so the revenue, receivable, and tax accounts are mapped once during setup and the Shopify tax rates map to DATEV tax codes. Shopify orders/paid and refund webhooks then trigger each booking, deduped on the webhook id and the order GID. Reading orders older than 60 days needs Shopify's read_all_orders scope, which requires Partner approval, so the backfill depth depends on that grant.

Related integrations

Connect DATEV and Shopify

Free to use. Add your credentials, ping your real systems, and see if we fit.

Get started