App Base Guide

Overview

This guide will walk you through the process of adding your own app to Shopware and configuring it, so it is able to communicate to your external backend server.

Prerequisites

If your're not familiar with the app system, please take a look at the concept first.

File structure

To get started with your app, create an apps folder inside the custom folder of your Shopware dev installation. In there, create another folder for your application and provide a manifest file in it.

└── custom
├── apps
│ └── MyExampleApp
│ └── manifest.xml
└── plugins

Manifest File

The manifest file is the central point of your app. It defines the interface between your app and the Shopware instance. It provides all the information concerning your app, as seen in the minimal version below:

manifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/Framework/App/Manifest/Schema/manifest-1.0.xsd">
<meta>
<name>MyExampleApp</name>
<label>Label</label>
<label lang="de-DE">Name</label>
<description>A description</description>
<description lang="de-DE">Eine Beschreibung</description>
<author>Your Company Ltd.</author>
<copyright>(c) by Your Company Ltd.</copyright>
<version>1.0.0</version>
<license>MIT</license>
</meta>
</manifest>

The name of your app, that you provide in the manifest file, needs to match the folder name of your app.

The app can now be installed by running the following command:

bin/console app:install --activate MyExampleApp

By default, your app files will be validated before installation, to skip the validation you may use the --no-validate flag.

Apps get installed as inactive. You can activate them by passing the --activate flag to the app:install command or by executing the app:activate command after installation.

For a complete reference of the structure of the manifest file take a look at the Manifest reference.

Setup

If your app backend server and Shopware need to communicate, it is necessary that a registration is performed during the installation of your app. This process is called setup. During the setup it is verified, that Shopware connects to the right backend server and keys are exchanged to secure all further communications. During the setup process your app backend will obtain credentials that can be used to authenticate against the Shopware API. Additionally your app will provide a secret that Shopware will use to sign all further requests it makes to your app backend, allowing you to verify that the incoming requests originate from authenticated Shopware installations.

The setup workflow is shown in the following schema, each step will be explained in detail.

Setup request workflow

Registration Request

The registration request is made as a GET-Request against a URL that you provide in the manifest file of your app.

manifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/Framework/App/Manifest/Schema/manifest-1.0.xsd">
<meta>
...
</meta>
<setup>
<registrationUrl>https://my.example.com/registration</registrationUrl>
</setup>
</manifest>

The following query parameters will be send with the request:

  • shop-id: The unique identifier of the shop, where the app was installed

  • shop-url: The URL of the shop, this can later be used to access the Shopware API

  • timestamp: The Unix timestamp when the request was created

An example request may look like this:

GET https://my.example.com/registration?shop-id=KIPf0Fz6BUkN&shop-url=http%3A%2F%2Fmy.shop.com&timestamp=159239728

Starting from Shopware version 6.4.1.0, the current shopware version will be sent as a sw-version header.

Additionally, the shopware-app-signature header will be provided, which contains a cryptographic signature of the query string. The secret used to generate this signature is the app secret, that is unique per app and will be provided by the Shopware Account if you upload your app to the store. This secret won't leave the Shopware Account, so it won't be even leaked to the shops installing your app.

You and the Shopware Account are the only parties that should know your app-secret, therefore make sure you never accidentally publish your app-secret.

For local development you can specify a <secret> in the manifest file, that is used for signing the registration request. However if a app uses a hard-coded secret in the manifest it can not be uploaded to the store.

To verify that the registration can only be triggered by authenticated Shopware shops you need to recalculate the signature and check that the signatures match, thus you've verified that the sender of the request possesses the app secret.

Following code snippet can be used to recalculate the signature:

PHP
PHP
use Psr\Http\Message\RequestInterface;
/** @var RequestInterface $request */
$queryString = $request->getUri()->getQuery();
$signature = hash_hmac('sha256', $queryString, $appSecret);

Registration Response

To verify that you are also in possession of the app secret you need to provide a proof that is signed with the app secret too. The proof consist of the sha256 hmac of the concatenated shopId, shopUrl and your app's name.

Following code snippet can be used to calculate the proof:

PHP
PHP
use Psr\Http\Message\RequestInterface;
/** @var RequestInterface $request */
$queryString = $request->getUri()->getQuery();
parse_str($queryString, $queryValues);
$proof = \hash_hmac(
'sha256',
$queryValues['shop-id'] . $queryValues['shop-url'] . $appname,
$appSecret
);

Besides the proof your app needs to provide a randomly generated secret, that should be used to sign every further request from this shop. Make sure to save the shopId, shopUrl and generated secret, so you can associate and use this information later.

This secret will be called shop-secret to distinguish it from the app-secret. The app-secret is unique for your app and is used to sign the registration request of every shop that installs your app. The shop-secret will be provided by your app during the registration and should be unique for every shop

The last thing needed in the registration response is a URL, which the confirmation request will be send to.

A sample registration response may look like this:

{
"proof": "94b42d39280141de84bd6fc8e538946ccdd182e4558f1e690eabb94f924e7bc7",
"secret": "random secret string",
"confirmation_url": "https://my.example.com/registration/confirm"
}

Confirmation Request

If the proof you provided in the registration response matched the one generated on the shop side the registration is completed. As a result your app will receive a POST request against the URL specified as the confirmation_url of the registration with the following parameters send in the request body:

  • apiKey: The ApiKey used to authenticate against the Shopware API

  • secretKey: The SecretKey used to authenticate against the Shopware API

  • timestamp: The Unix timestamp when the request was created

  • shopUrl: The URL of the shop

  • shopId: The unique identifier of the shop

The payload of that request may look like this:

{
"apiKey":"SWIARXBSDJRWEMJONFK2OHBNWA",
"secretKey":"Q1QyaUg3ZHpnZURPeDV3ZkpncXdSRzJpNjdBeWM1WWhWYWd0NE0",
"timestamp":"1592398983",
"shopUrl":"http:\/\/my.shop.com",
"shopId":"sqX6cqHi6hbj"
}

Make sure that you save the api-credentials for that shopId.

Starting from Shopware version 6.4.1.0, the current shopware version will be sent as a sw-version header.

The request is signed with the shop-secret, that your app provided in the registration response and the signature can be found in the shopware-shop-signature header. You need to recalculate that signature and check that it matches the provided one, to make sure that the request is really send from shop with that shopId.

You can use following code snippet to generate the signature:

PHP
PHP
use Psr\Http\Message\RequestInterface;
/** @var RequestInterface $request */
$hmac = \hash_hmac('sha256', $request->getBody()->getContents(), $shopSecret);

Permissions

Shopware comes with the possibility to create fine grained Access Control Lists (ACLs). That means that that you need to request permissions if your app needs to read or write data over the API or wants to receive webhooks. The permissions your app needs are defined in the manifest file and are composed of the privilege (read, create, update, delete) and the entity.

Sample permissions to read, create and update products, as well as delete orders look like this:

manifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/Framework/App/Manifest/Schema/manifest-1.0.xsd">
<meta>
...
</meta>
<permissions>
<read>product</read>
<create>product</create>
<update>product</update>
<delete>order</delete>
</permissions>
</manifest>

The permissions you request need to be accepted by the user during the installation of your app. After that these permissions are granted for your app and your API access through the credentials from the confirmation request of the setup workflow are limited to those permissions.

Keep in mind that read permissions also extend to the data contained in the requests so that your app needs read permissions for the entities contained in the subscribed webhooks.

Webhooks

With webhooks you are able to subscribe to events occurring in Shopware. Whenever such an event occurs a POST request will be send to the specified URL.

To use webhooks in your app, you need to implement a <webhooks> element in your manifest file, like this:

manifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/Framework/App/Manifest/Schema/manifest-1.0.xsd">
<meta>
...
</meta>
<webhooks>
<webhook name="product-changed" url="https://example.com/event/product-changed" event="product.written"/>
</webhooks>
</manifest>

This example illustrates you how to define a webhook with the name product-changed and the url https://example.com/event/product-changed which will be triggered if the event product.written is fired. So every time a product is changed, your custom logic will get executed. Further down you will find a list of the most important events you can hook into.

An event contains as much data as is needed to react to that event. The data is json contained in the request body. For example:

{
"data":{
"payload":[
{
"entity":"product",
"operation":"delete",
"primaryKey":"7b04ebe416db4ebc93de4d791325e1d9",
"updatedFields":[
]
}
],
"event":"product.written"
},
"source":{
"url":"http:\/\/localhost:8000",
"appVersion":"0.0.1",
"shopId":"dgrH7nLU6tlE"
}
}

Where the source property contains all necessary information about the Shopware instance that send the request:

  • url is the url under which your app can reach the Shopware instance and its api

  • appVersion is the version of the app that is installed

  • shopId is the id by which you can identify the Shopware instance

The next property data contains the name of the event so that a single endpoint can handle several different events, should you desire. data also contains the event data in the payload property, due to the asynchronous nature of theses webhooks the payload for entity.written events does not contain complete entities as these might become outdated. Instead the entity in the payload is characterized by its id, stored under primaryKey, so that the app can fetch additional data through the shops API. This also has the advantage of giving the app explicit control over the associations that get fetched instead of relying on the associations determined by the event. Other events in contrast contain the entity data that defines the event, but keep in mind that event might not contain all associations.

Starting from Shopware version 6.4.1.0, the current shopware version will be sent as a sw-version header.

You can verify the authenticity of the incoming request by checking the shopware-shop-signature every request should have a sha256 hmac of the request body, that is signed with the secret your app assigned the shop during the registration. The mechanism to verify the request is exactly the same as the one used for the confirmation request.

You can use a variety of events to react to changes in Shopware that way. See the table below for an overview of most important ones.

Event

Description

Permissions needed

contact_form.send

Triggers if a contact form is send

-

mail.sent

Triggers if a mail is send from Shopware

-

mail.before.send

Triggers before a mail is send

-

checkout.order.placed

Triggers if an order is placed checkout-wise

order:read

checkout.customer.register

Triggers if a new customer was registered yo

customer:read

checkout.customer.login

Triggers as soon as a customer logs in

customer:read

checkout.customer.double_opt_in_guest_order

Triggers as soon as double opt-in is accepted in a guest order

customer:read

checkout.customer.before.login

Triggers as soon as a customer logs in within the checkout process

customer:read

checkout.customer.changed-payment-method

Triggers if a customer changes his payment method in checkout process

customer:read

checkout.customer.logout

Triggers if a customer logs out

customer:read

checkout.customer.double_opt_in_registration

Triggers if a customer commits to his registration via double opt in

customer:read

customer.recovery.request

Triggers if a customer recovers his password

customer_recovery:read

product.written

Triggers if a product is written

product:read

product_price.written

Triggers if product price is written

product_price:read

category.written

Triggers if a category is written

category:read

App lifecycle events

Apps can also register to lifecycle events of its own lifecycle, namely its installation, updates and deletion. For example they maybe used to delete user relevant data from your data stores once somebody removes your app from their shop.

Event

Description

app.installed

Triggers once the app is installed

app.updated

Triggers if the app is updated

app.deleted

Triggers once the app is removed

app.activated

Triggers if an inactive app is activated

app.deactivated

Triggers if an active app is deactivated

Example request body:

{
"data":{
"payload":[
],
"event":"app_deleted"
},
"source":{
"url":"http:\/\/localhost:8000",
"appVersion":"0.0.1",
"shopId":"wPNrYZgArBTL"
}
}

Validation

You can run the app:validate command to validate the configuration of your app. It will check for common errors, like:

  • non-matching app names

  • missing translations

  • unknown events registered as webhooks

  • missing permissions for webhooks

  • errors in the config.xml file, if it exists

To validate all apps in your custom/apps folder run:

bin/console app:validate

Additionally, you can specify which app should be validated by providing the app name as an argument;

bin/console app:validate MyExampleApp

API Docs

get
registration

https://my.example.com
Request
Response
Request
Headers
shopware-app-signature
required
string
The hmac-signature of the query string, signed with the app secret
Query Parameters
timestamp
required
integer
The current Unix timestamp when the request was created
shop-url
required
string
The URL of the shop, where the app was installed, can be used to access to the Shopware API
shop-id
required
string
The unique identifier of the shop, where the app was installed
Response
200: OK
{
"proof": "94b42d39280141de84bd6fc8e538946ccdd182e4558f1e690eabb94f924e7bc7",
"secret": "random secret string",
"confirmation_url": "https://my.example.com/registration/confirm"
}

post
confirmation

https://my.example.com
Request
Response
Request
Headers
shopware-shop-signature
required
string
The hmac-signature of the body content, signed with the shop secret returned from the registration request
sw-version
required
string
Starting from Shopware version 6.4.1.0, the current shopware version will be sent as a sw-version header.
Body Parameters
shopId
required
string
The unique identifier of the shop
shopUrl
required
string
The URL of the shop
timestamp
required
integer
The current Unix timestamp when the request was created
secretKey
required
string
SecretKey used to authenticate against the Shopware API
apiKey
required
string
ApiKey used to authenticate against the Shopware API
Response
200: OK