> For the complete documentation index, see [llms.txt](https://developer.vario-software.de/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.vario-software.de/documentation/apps/app-backends.md).

# App Backends

Most apps only *read and write* ERP data. The apps in this space do something more: they **take over a role** in the ERP. To do that, an app registers a **backend** — a record in the ERP that says *"for this domain, I am responsible"*.

{% hint style="info" %}
Every domain in this space works the same way. Learn it once here.
{% endhint %}

## The pattern

```
┌───────────┐   1. install: register backend   ┌───────────┐ 
│  Your App │ ──────────────────────────────►  │ VARIO ERP │ 
│           │                                   │           │ 
│           │ ◄──────────────────────────────   │           │ 
└───────────┘   2. ERP calls back / notifies    └───────────┘
```

{% stepper %}
{% step %}

## On install

A migration creates a backend record carrying your **app identifier**.
{% endstep %}

{% step %}

## From then on

The ERP routes work in that domain to you — either by calling a URL you registered, or by firing a [webhook](https://developer.vario-software.de/documentation/webhooks/introduction).
{% endstep %}
{% endstepper %}

Two things make the record yours:

| Field                  | Value                | Why                                                                  |
| ---------------------- | -------------------- | -------------------------------------------------------------------- |
| `appId`                | your `appIdentifier` | The ERP knows which app owns this backend, and you can find it again |
| `type` / `backendType` | `APP`                | Distinguishes an app-provided backend from a built-in one            |

## What each domain registers

| Domain                                                                                           | Record                                 | Endpoint                                               |
| ------------------------------------------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------ |
| [Onlineshops and Marketplaces](/documentation/apps/app-backends/onlineshops-and-marketplaces.md) | Sales channel backend + sales channels | `POST /community/{version}/erp/sales-channels/backend` |
| [Shipping](/documentation/apps/app-backends/shipping-carrier.md)                                 | Carrier type                           | `POST /community/{version}/vds/carrier-type`           |
| [Banking](/documentation/apps/app-backends/banking.md)                                           | Bank backend                           | `POST /erp/bank/backend`                               |
| [Financial Accounting](/documentation/apps/app-backends/financial-accounting.md)                 | Finance backend                        | `POST /community/{version}/erp/finance/backend`        |
| [POS Payment Methods](/documentation/apps/app-backends/pos-payment-methods.md)                   | POS payment backend                    | `POST /community/{version}/erp/pos/payment/backend`    |

The framework provides migration helpers for sales channel, bank and finance backends. Carrier types and POS payment backends are created with a plain API call.

{% hint style="info" %}
The naming is not uniform. Most domains have something literally called a *backend*; shipping does not — there the **carrier type** (`Versendertyp`) plays that role, carrying the app identifier and the callback URLs. There is no "carrier backend".
{% endhint %}

## How the ERP reaches you

This is the part that surprises people: for several domains the ERP calls **into your app**, synchronously. You register those URLs on the backend record itself.

| Domain   | URL fields on the record                             | Called when                                                     |
| -------- | ---------------------------------------------------- | --------------------------------------------------------------- |
| Shipping | `createShipmentSyncUrl`, `createLabelSyncUrl`        | A shipment is created / a label is requested                    |
| Banking  | `appTransactionUrl`, `appPaymentUrl`, `appStatusUrl` | Transactions are fetched / a payment is sent / status is polled |

{% hint style="warning" %}
Your app must be publicly reachable for these to work. The URL is built from your app's public host at install time — which means **if that host changes, the stored URLs are stale**. Every shipping app ships a later migration that rewrites them for exactly this reason.
{% endhint %}

## What you register vs. what the customer creates

A frequent misunderstanding. Your app registers the **technical infrastructure**. The customer creates their own **business objects** on top of it:

| You create (on install)                  | The customer creates (in the ERP)                                       |
| ---------------------------------------- | ----------------------------------------------------------------------- |
| Carrier **type** ("DHL App")             | Carriers and shipping methods that use it                               |
| Bank **backend** ("PayPal")              | Their banks, which select your backend                                  |
| POS payment **backend**                  | The payment methods that point at it, and their assignment to registers |
| Sales channel **backend** ("Shopware 6") | Usually created by your app — one channel per store                     |

So a shipping app never creates a carrier, and a banking app usually never creates a bank — it makes itself *selectable*. Don't create customer master data on install; the customer owns names, IBANs and BICs.

{% hint style="warning" %}
**Deactivate, don't delete, on uninstall.** Customer objects that reference your backend must survive an uninstall so they can be reactivated on reinstall. Deactivate in dependency order (children before parents) — the ERP validates references to inactive parents.
{% endhint %}

## Migrations

Backends are created in a **migration** that runs on install. Two things you must understand:

{% hint style="info" %}
**Migrations run once, ever.** Each step is recorded by name. Editing an existing step does nothing on tenants where it already ran — you must add a **new, differently named step**.
{% endhint %}

```javascript
const migratorErp = new MigratorErp('migration1'); 

await migratorErp.setMigration('salesChannel', async (methods) =>
{ 
  const backend = await methods.createSalesChannelBackend('My Shop', ['ECOMMERCE']); 
  await methods.createSalesChannel(backend, 'My Shop', '', 'ECOMMERCE'); 
  await methods.activateSalesChannelBackend(backend); 
});
```

Use `always(key, callback)` instead of `setMigration` for steps that must re-run on every install — webhook registration is the usual case.

{% hint style="warning" %}
**A failed migration does not fail the install.** Errors are caught, reported to your error handler and written to the app log — the install still reports success. If a backend seems to be missing, read the app log. Failed steps are not recorded as done, so they retry on the next install.
{% endhint %}

{% hint style="warning" %}
**Order matters.** Later steps that look up what earlier steps created will fail if the order is wrong — for example, an EAV group must exist before it can be read or changed, and a bank cannot reference a backend that isn't there yet.
{% endhint %}

## Sandbox vs. production

There is no platform-level sandbox flag. Two approaches are used:

* **A second backend record** whose label is suffixed `(Sandbox)` — the shipping apps do this, and detect the mode from the label.
* **A configuration value** the user toggles — PayPal does this.

{% hint style="warning" %}
Configuration values round-trip through storage as **strings**. The string `"false"` is truthy in JavaScript, so compare explicitly (`value === 'true'`) rather than relying on truthiness. This has caused real incidents where a sandbox flag sent live traffic to production.
{% endhint %}

## Finding your backend again

You rarely keep the id. Query it back by your own app identifier — e.g. via [VQL](https://developer.vario-software.de/documentation/fundamentals/vql):

```sql
SELECT id 
  FROM sales-channel.salesChannelBackends 
 WHERE appId = '<your-app-identifier>' 
   AND type = 'APP'
```

The framework's `findSalesChannelBackend()` does exactly this.

## Checklist

* Manifest declares the permissions your domain needs
* Migration registers the backend with your `appId` and type `APP`
* Callback URLs point at your public host — and a later migration can refresh them
* Webhooks registered with `always()`, not `setMigration()`
* Uninstall deactivates (never deletes) customer objects, children first
* Sandbox mode explicit, with string-safe boolean checks
* App log checked after install — failures are silent


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developer.vario-software.de/documentation/apps/app-backends.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
