We're constantly trying to improve your support experience, and your feedback is extremely valuable to us.

Please take a moment to tell us about your experience today.
Sign up for future Help Center user research studies.

Marketing activity endpoints

Before your app can receive communication from Shopify through a marketing activity app extension, you need to host a series of standardized API endpoints on your app's primary domain. These endpoints are called when your app extension is rendered and when there's a related action requested from your app.

Endpoint requirements

The requirements for the marketing activity app endpoints are as follows:

rule/concern type/requirement
API format REST
Content type JSON
Security mechanism HMAC/Signed requests (same as webhooks)
Protocol HTTPS (app domain requires valid SSL certificate)

Base endpoint

Your app needs to host a base endpoint so Shopify can call it when integrating in the different use cases. This endpoint must be hosted under your app's URL. You can specify a custom namespace/subpath. If your app has multiple extensions, then you can share a single namespace.

The following example shows a typical endpoint:

https://app-url.com/api/marketing_activities

The URL includes:

  1. An HTTPS protocol (the app must have a valid SSL certificate)
  2. The base application URL
  3. A fixed namespace (configured in the app extension)

Therefore the base endpoint is:

https://#{base_app_url}/#{custom_namespace}

Preload a marketing activity form

When a merchant uses a marketing activity extension, the first thing that Shopify does is retrieve the form configuration from the app extensions framework. The form configuration is a schema that lists all form fields and other UI components that should be rendered in the UI.

HTTP request

POST "#{base_endpoint}/preload_form_data"

Expected HTTP status response:

  • 200 - OK
  • 412 - Precondition Failed (if the app can't create the activity for some reason, such as the user hasn't finished setting up the app)

JSON input

{
  "shopify_domain": "johns-apparel.myshopify.com",
  "shop_id": "gid://shopify/Shop/1111111",
  "user_id": 1,
  "locale": "en"
}
shopify_domain The shop domain requesting to preload the form. Apps hold the shop domain in their database as a unique record, and use it to fetch specific data for each merchant or shop.
shop_id The shop GID.
user_id The ID of the user who is currently logged in to the Shopify admin. You can use the ID to link the authenticated user with an ad account instead of a broad ad account shared with many different users for a given shop.
locale The current session locale (string), which is the language being used in the current session by the merchant.
marketing_activity_id (optional) The existing marketing activity GID. This is used only when editing a marketing activity (to learn more, see Edit a marketing activity).

JSON output

You can include a field in the response if you want to change a property of the marketing activity. If nothing is returned, then whatever is configured statically for that field (inside the app extension) is what will be rendered to the merchant.

Here's an example of an empty response:

{
  "form_data": {}
}

In this example, an app adds more attributes to average_daily_budget field:

{
  "form_data": {
    "average_daily_budget": {
      "currency": "CAD",
      "help_text": "Your shop will perform best with a $20 daily budget.",
      "min_amount": "13.00"
    }
  }
}

Since these properties will be merged into the current state of average_daily_budget, if there was a predefined help_text, then the app would overwrite with Your shop will perform best with a $20 daily budget.. This would be true also for other fields present in that response. The state of the amount field is preserved since the app didn't mention it in the response.

If an app wants to clear the amount field, then it needs to provide an empty value in the response:

{
  "form_data": {
    "average_daily_budget": {
      "amount": "",
      "currency": "CAD",
      "help_text": "Your shop will perform best with a $20 daily budget.",
      "min_amount": "13.00"
    }
  }
}

FAQ

Which properties are available for each field? What is the contract for each component?

To learn more about how to configure the components, see the Marketing activity components reference.

Is it possible for an app to add a new field in the UI dynamically?

No. Apps must always respect the predefined fields from the static definition of the form (as it was created from the Partners Dashboard). However, an app can show, hide, or disable UI fields using the pattern above.

Adding marketing activity form banners

In the preload call, you can return a list of banners to display on top of the form in addition to returning the preloaded form configuration and values. These app banners are useful for providing merchants with educational information separate from the error messages in the marketing activity form.

There are a few different types of app banners available (see the entry for type in the App banner properties table below). To see how each one looks, refer to the Banner documentation in Shopify's Polaris design system.

Preload call with app banners response JSON

 "form_data": {
   "average_daily_budget": {
     "value": 35,
     "description": "in CAD",
     "minimum": 13
   }
 },
 "app_banners":[
   {
     "type": "info",
     "title": "Your Dynamic Product Ad might not be effective. Please refer to this [help](https://www.teststore.myshopify.com/extension/create)",
     "description": "Please create a carousel ad instead.",
     "primary_cta": {
       "label": "Create a carousel Ad",
       "url": "https://www.teststore.myshopify.com/extension/create",
     },
     "secondary_cta": {
       "label": "Find out more",
       "url": "https://www.teststore.myshopify.com/facebook_ads/find_out_more",
     },
   }
 ]

App banner properties

type (required) The type of the banner. Valid values: default, info, warning, critical.
title (required) The title of the banner.
description (optional) The description that appears in the banner.
primary_cta (optional) The primary call to action button in the banner. Inlcudes the following properties:
  • label - The label of the call to action button.
  • url - The link of the call to action button.
secondary_cta (optional) The secondary call to action button in the banner. Requires that primary_cta is provided. Accepts the same properties as primary_cta.

App banner FAQ

What if I don't need to display any app banners in the form?

In the preload response, you can return nil or an empty array instead of returning the hash with the app_banners key.

Is markdown supported in the app banner?

Only markdown formatting for links is currently supported. The link urls can be either absolute or relative.

Are dismissible app banners supported?

No, they aren't currently supported.

Preview a marketing activity

Most marketing activities let the merchant preview them before they are actually created. For example, when a merchant creates a Facebook campaign, they can preview it after choosing different options, content, types, some descriptions, and how much they want to spend.

To preview a marketing activity, the app receives the marketing activity from the form and then returns a preview URL. This preview URL is embedded in Shopify in an iframe and displayed to the merchant.

Previewing a marketing activity is a good way to perform validations and give early feedback to the merchant. It's a good idea to perform validations before generating the preview. If there are validation errors, then return them in the response.

HTTP request

POST "#{base_endpoint}/preview"

Expected HTTP status responses:

  • 200 - OK (if the preview was generated and no validation errors were found)
  • 422 - Unprocessable Entity (if there are validation errors)

JSON input

{
  "shopify_domain": "johns-apparel.myshopify.com",
  "shop_id": "gid://shopify/Shop/1111111",
  "user_id": 1,
  "locale": "en",
  "marketing_activity_id": "gid://shopify/MarketingActivity/4",
  "preview_types": ["desktop", "mobile"],
  "properties": {
    "average_daily_budget": "13.00",
    "quantity": 2
  }
}
shopify_domain The associated shop domain.
shop_id The associated shop GID.
user_id The ID of the authenticated user.
marketing_activity_id (optional) The ID of the marketing activity. This is passed only if the merchant is previewing a marketing activity that already exists.
locale The current locale (string).
preview_types An array of preview types to request. Default: ["desktop", "mobile"].
properties All input field values collected from the merchant.

JSON output

In this example, the app was able to generate a preview (status code 200):

{
   "desktop": {
     "preview_url": "https://app-url.com/previews/98564321",
     "content_type": "text/html",
     "width": 1000,
     "height": 800
   },
   "mobile": {
     "preview_url": "https://app-url.com/previews/98564322",
     "content_type": "text/html",
     "width": 360,
     "height": 800
   }
}
preview_type The corresponding preview_type key corresponding to what was passed in the JSON request.
preview_url (required when 200) The URL with the preview HTML or image. Shopify embeds this preview inside an iframe.
content_type (required when 200) The mime type for the preview. Available options:
  • text/html
  • image/jpeg
  • image/png
width (required when 200) The width of the preview (Integer). Shopify uses this to adjust the size of the iframe.
height (required when 200) The height of the preview (Integer). Shopify uses this to adjust the size of the iframe.

In this example, the app was unable to generate a preview due to validation errors (status code 422):

{
  "errors": {
    "base": [
      "Your account does not have any payment method set"
    ],
    "quantity": [
      "is required"
    ],
    "start_at": {
      "start_time": [
        "is required",
        "must be after January 1st, 2019"
      ],
      "end_time": [
        "is required"
      ]
    }
  }
}
errors (required when 422)
  • The keys represent the input name of the form field.
  • The array of strings contains the error messages associated to that field.
  • Error messages can contain markdown links using root-relative or HTTPS URLs. For example, Click [here](/admin/app) to set up your account or Read more about [account setup](https://example.com).
  • base is a generic error not associated directly to one specific field.
  • This structure is roughly following the same convention to serialize errors from ActiveResource objects.
  • Nested components (like the budget_settings example) nest their inner fields so it's easier to read and locate the specific error for each field.

FAQ

Does the preview URL support HTML?

Yes.

Are asynchronous previews supported?

No. Apps should provide synchronous preview endpoints only.

If your app requires more than 10 seconds to generate a preview, then as a workaround you can provide the preview endpoint quickly as HTML and generate the preview when the URL gets hit.

Which preview types are supported?

You can define up to 3 preview types in the form builder. The endpoint returns only the preview types that are requested.

I have all the information I need to generate the preview from the properties in the request. Do I need to use marketing_activity_id?

The marketing_activity_id is provided in the request when previewing an existing marketing activity. You can use it to generate the preview, but it isn't required.

Create a marketing activity

This endpoint is called when a merchant tries to create a marketing activity in the marketing section. The marketing activity create REST end point must schedule an asynchronous job to create a marketing activity in the marketing platform. This endpoint must validate the form and schedule an update to the marketing activity object created by Shopify. In the job, the first update to the marketing activity object must include the UTM parameters, and if it is a paid activity, also the budget fields.

The workflow for creating a marketing activity includes the following steps:

  1. The merchant fills out the marketing activity form, and clicks on the Publish button.

  2. Shopify creates the marketing activity in advance and hits the app's create marketing activity REST endpoint.

  3. The app should do the following:

    1. Validate the form. If it's valid, then respond with 200 OK and empty JSON response. If it's invalid, then respond with the correct HTTP error code and the JSON error hash as described in previewing section.
    2. Schedule an asynchronous job to set the UTM parameter and budget of the marketing activity using MarketingActivityUpdate.
    3. Schedule an asynchronous job to publish the marketing activity to the platform (such as Facebook), then invoke MarketingActivityUpdate to update the status.
  4. If the app responds with 200 OK, then Shopify responds back to the app with the created marketing activity in a Pending state.

  5. If the app responds with an HTTP error code in the 400s or 500s, then Shopify cleans up the marketing activity it created in advance.

  6. After the app has published the marketing activity to the platform, it should do the following:

    1. If it's successful, then call MarketingActivityUpdate to mark the activity either as ACTIVE if it's ongoing, or as INACTIVE if it's completed.
    2. If it's unsuccessful, then call MarketingActivityUpdate to mark the activity as FAILED, and pass the cause of the error so that it can be displayed on the Marketing page in the Shopify admin.

Sequence diagram of marketing activity creation flow

Sequence diagram of marketing activity creation flow.

HTTP request

POST "#{base_endpoint}"

Expected HTTP status responses:

  • 200 - OK (if the activity was created and no validation errors were found)
  • 412 - Precondition failed (if the user hasn't finished setting up the app)
  • 422 - Unprocessable Entity (if there are validation errors)

JSON input

The JSON input for creating a marketing activity is almost the same as the input for previewing a marketing activity:

{
  "shopify_domain": "johns-apparel.myshopify.com",
  "shop_id": "gid://shopify/Shop/1111111",
  "user_id": 1,
  "locale": "en",
  "marketing_activity_title": "Apparel promotion",
  "marketing_activity_id": "gid://shopify/MarketingActivity/34435",
  "context": "",
  "properties": {
    "average_daily_budget": "150.00",
    "quantity": 2
  }
}
shopify_domain The associated shop domain.
shop_id The associated shop GID.
user_id The ID of the authenticated user.
locale The current locale (string).
marketing_activity_title The title of the marketing activity (string).
marketing_activity_id The GID of the marketing activity that was created by Shopify.
context(deprecated) An encoded string that Shopify uses to identify some marketing activity properties. Note: This context needs to be relayed to Shopify if you are using the deprecated GraphQL MarketingActivityCreate endpoint.
properties All input field values collected from the merchant.

JSON output

If the app was able to create the marketing activity (status code 200), we expect an empty JSON response.

{}
errors (required when 422) If the app detects a validation error, then it will receive an error. The error responses are the same as those for [previewing a marketing activity](#preview-a-marketing-activity).

FAQ

Why does Shopify create the marketing activity in advance?

This simplifies the process of creating a marketing activity by preventing the app from having to make an extra call to Shopify. It also reduces the chance of timeouts for this REST endpoint.

Why should we asynchronously update the marketing activity?

The marketing activity creation flow will be more responsive. It will take less time to respond to the initial call to the App.

What is the initial status of the marketing activity?

The initial status is PENDING. In the App's asynchronous job to update the marketing activity object, it is free to update to other status such as ACTIVE or FAILED or continue keep the status as PENDING.

Why do I need to update the marketing activity UTM parameters?

Shopify needs UTM parameters in order to display the engagement and the report for the ad. This is required for all marketing tactics except the storefront app tactic. The UTM parameter must be specified the first time the marketing activity is updated.

Do apps need to call both the marketing activity endpoints and the MarketingEvent API?

No. Apps that create marketing activities through the new marketing section will use only the marketing activity endpoints.

Will I lose old marketing events from my app?

No. Shopify will backfill all existing marketing events into marketing activities. Each marketing event will have a corresponding marketing activity, and any changes to either API will be reflected in both entities.

Edit a marketing activity

In some cases, the merchant is allowed to change some attributes of an existing marketing activity. For example, some social media apps let the user change the budget of an activity after it's been created. Some other apps have more flexibility, and let the user update additional fields.

This can be achieved by using the preload_form_data endpoint (see Preload a marketing activity form).

In this example, Shopify sends the marketing_activity_id in the payload:

{
  "shopify_domain": "johns-apparel.myshopify.com",
  "shop_id": "gid://shopify/Shop/1111111",
  "user_id": 1,
  "locale": "en",
  "marketing_activity_id": "gid://shopify/MarketingActivity/34526"
}

The app must make sure that it has that marketing activity persisted in their database and respond accordingly, setting up the form fields as expected:

{
  "form_data": {
    "average_daily_budget": {
      "amount": "1000.00",
      "currency": "CAD",
      "help_text": "Your shop will perform best with a $20 daily budget.",
      "min_amount": "13.00"
    },
    "quantity": {
      "disabled": true,
      "value": 2
    },
    "ad_title": {
      "disabled": true,
      "value": "Welcome to John's apparel store."
    }
  }
}

Update a marketing activity

This is the continuation of the previous use case. After all changes were made, the merchant submits the form. This action is similar to inline marketing activity creation.

HTTP request

PATCH "#{base_endpoint}"

Expected HTTP status responses:

  • 200 - OK (if the activity was synchronously updated and no validation errors were found)
  • 202 - Accepted (if the activity was scheduled to be asynchronously updated)
  • 412 - Precondition Failed (if the user hasn't finished setting up the app)
  • 422 - Unprocessable Entity (if there are validation errors)

After receiving a 200 response, Shopify updates the marketing activity's status to ACTIVE.

JSON input

The input to update a marketing activity is the same as inline marketing activity creation with the marketing activity GID:

{
  "shopify_domain": "johns-apparel.myshopify.com",
  "shop_id": "gid://shopify/Shop/1111111",
  "user_id": 1,
  "locale": "en",
  "marketing_activity_id": "gid://shopify/MarketingActivity/34435",
  "marketing_activity_title": "Apparel promotion",
  "properties": {
    "average_daily_budget": 150.00,
    "quantity": 2
  }
}
shopify_domain The associated shop domain.
shop_id The associated shop GID.
user_id The ID of the authenticated user.
locale The current locale (string).
marketing_activity_id The GID of the marketing activity being updated.
marketing_activity_title The title of the marketing activity (string).
properties All input field values.

JSON output

If your app is able to update the marketing activity synchronously (status code 200), then Shopify expects an empty body in the response.

If your app has started to update the marketing activity asynchronously, then Shopify expects the status code 202 accepted and an empty body in the response.

However, if your app detects validation errors, then it will receive a 422 status code and the related errors (similar to the JSON input in the Preview a marketing activity section).

Pause a marketing activity

You can pause a marketing activity that is in an active state. You can use the pause endpoint to pause an active marketing activity either synchronously or asynchronously.

HTTP request

PATCH "#{base_endpoint}/pause"

Expected HTTP status responses:

  • 200 - OK (if the activity was synchronously paused)
  • 202 - Accepted (if the activity has started asynchronous pause)
  • 412 - Precondition Failed (if the user hasn't finished setting up the app)

After receiving a 200 response, Shopify updates the marketing activity's status to PAUSED.

JSON input

The input to update a marketing activity is the same as inline marketing activity creation with the marketing activity GID:

{
  "shopify_domain": "johns-apparel.myshopify.com",
  "shop_id": "gid://shopify/Shop/1111111",
  "user_id": 1,
  "locale": "en",
  "marketing_activity_id": "gid://shopify/MarketingActivity/34435",
}
shopify_domain The associated shop domain.
shop_id The associated shop GID.
user_id The ID of the authenticated user.
locale The current locale (string).
marketing_activity_id The GID of the marketing activity being updated.

JSON output

If your app is able to pause the marketing activity synchronously (status code 200), then Shopify expects an empty body in the response.

If your app has started to pause the marketing activity asynchronously, then Shopify expects the status code 202 accepted and an empty body in the response.

Resume a marketing activity

You can resume marketing activities that are in a paused state. You can use the resume endpoint to resume a paused marketing activity either synchronously or asynchronously.

HTTP request

PATCH "#{base_endpoint}/resume"

Expected HTTP status responses:

  • 200 - OK (if the activity was synchronously resumed)
  • 202 - Accepted (if the activity was asynchronously resumed)
  • 412 - Precondition Failed (if the user hasn't finished setting up the app)

After receiving a 200 response, Shopify updates the marketing activity's status to ACTIVE.

JSON input

The input to update a marketing activity is the same as inline marketing activity creation with the marketing activity GID:

{
  "shopify_domain": "johns-apparel.myshopify.com",
  "shop_id": "gid://shopify/Shop/1111111",
  "user_id": 1,
  "locale": "en",
  "marketing_activity_id": "gid://shopify/MarketingActivity/34435",
}
shopify_domain The associated shop domain.
shop_id The associated shop GID.
user_id The ID of the authenticated user.
locale The current locale (string).
marketing_activity_id The GID of the marketing activity being updated.

JSON output

If your app is able to resume the marketing activity synchronously (status code 200), then Shopify expects an empty body in the response.

If your app has started to resume the marketing activity asynchronously, then Shopify expects the status code 202 accepted and an empty body in the response.

Republish a marketing activity

If a marketing activity creation fails, then its state changes to failed. To re-publish it, the user might need to modify a few of the activity's properties. You can use the republish endpoint to achieve this. The related request and response bodies are the same as those used to update a marketing activity.

HTTP request

POST "#{base_endpoint}/republish"

Expected HTTP status responses:

  • 200 - OK (if the activity was synchronously republished and no validation errors were found)
  • 202 - Accepted (if the activity has started asynchronous republish)
  • 412 - Precondition Failed (if the user hasn't finished setting up the app)
  • 422 - Unprocessable Entity (if there are validation errors)

After receiving a 200 response, Shopify updates the marketing activity's status to ACTIVE.

JSON input

The input to republish a marketing activity is the same as inline marketing activity creation with the marketing activity GID: json { "shopify_domain": "johns-apparel.myshopify.com", "shop_id": "gid://shopify/Shop/1111111", "user_id": 1, "locale": "en", "marketing_activity_id": "gid://shopify/MarketingActivity/34435", "marketing_activity_title": "Apparel promotion", "properties": { "average_daily_budget": "150.00", "quantity": 2 } }

shopify_domain The associated shop domain.
shop_id The associated shop GID.
user_id The ID of the authenticated user.
locale The current locale (string).
marketing_activity_id The GID of the marketing activity being updated.
marketing_activity_title The title of the marketing activity (string).
properties All input field values.

JSON output

If your app is able to republish the marketing activity synchronously (status code 200), then Shopify expects an empty body in the response.

If your app has started to republish the marketing activity asynchronously, then Shopify expects the status code 202 accepted and an empty body in the response.

However, if your app detects validation errors, then it will receive a 422 status code and the related errors (similar to the JSON input in the Preview a marketing activity section).

Delete a marketing activity

You can delete a marketing activity. You can use the delete endpoint to delete a marketing activity either synchronously or asynchronously.

HTTP request

PATCH "#{base_endpoint}/delete"

Expected HTTP status responses:

  • 200 - OK (if the activity was synchronously deleted)
  • 202 - Accepted (if the activity was asynchronously deleted)
  • 412 - Precondition Failed (if the user hasn't finished setting up the app)

After receiving a 200 response, Shopify updates the marketing activity's status to DELETED.

JSON input

The input to delete a marketing activity is the same as inline marketing activity creation with the marketing activity GID:

{
  "shopify_domain": "johns-apparel.myshopify.com",
  "shop_id": "gid://shopify/Shop/1111111",
  "user_id": 1,
  "locale": "en",
  "marketing_activity_id": "gid://shopify/MarketingActivity/34435",
}
shopify_domain The associated shop domain.
shop_id The associated shop GID.
user_id The ID of the authenticated user.
locale The current locale (string).
marketing_activity_id The GID of the marketing activity being deleted.

JSON output

If your app is able to delete the marketing activity synchronously (status code 200), then Shopify expects an empty body in the response.

If your app has started to delete the marketing activity asynchronously, then Shopify expects the status code 202 accepted and an empty body in the response.

Dynamic standalone fields

Some UI fields, such as type-ahead fields, require more comprehensive integration with an app to provide dynamic data from the backend.

In this example, the app wants to let the merchant partially type some interests and then provide a list of the available matching interests:

An example of the Audience section of the marketing activity form.

With that approach, the app directs the merchant towards a valid selection instead of using an open text field where the merchant can enter input that risks causing validation errors.

HTTP request

POST "#{base_endpoint}/load_field/#{field_name}"

Expected HTTP status responses:

  • 200 - OK

JSON input

{
  "shopify_domain": "johns-apparel.myshopify.com",
  "shop_id": "gid://shopify/Shop/1111111",
  "user_id": 1,
  "locale": "en",
  "field_name": "interests",
  "value": "sports"
}
shopify_domain The associated shop domain.
shop_id The associated shop GID.
user_id The ID of the authenticated user.
locale The current locale (string).
field_name The name of the field, specified when you create the form.
value The current value for the given field.

JSON output

Similar to other examples above, the app will customize the field properties but in this case it can manipulate only one field:

{
  "field_data": {
    "options": [
      {"label": "Sports", "value": "sports"},
      {"label": "Movies", "value": "movies"},
      {"label": "Music", "value": "music"}
    ]
  }
}

FAQ

Why does the endpoint include the field name? Why not have a single endpoint and send the field name in the payload only?

By having different endpoints for each field, you can help split the required code into smaller sections.

Why is the field name in both the payload and the URI?

Having more data in the payload can help create different HMACs for each call. This improves the security of our communications by having more random data to influence the HMAC.

Apps can use either the field name from the URI or the payload (both have the same effect).

Error feedback

This endpoint is called to report back to the app if the JSON output for any of the previously described endpoints does not conform to the expected format. For example, if the app response to the prealod endpoint contains an invalid data type, a missing key, or any unexpected value, then the error endpoint reports that back to the app.

Example invalid JSON output on preload:

{
  "form_data": {
    "ad_title": {
      "random_prop": "random",
      "min_length": -3
    },
    "average_daily_budget": {
      "help_text": "Your shop will perform best with a $20 daily budget.",
      "min_amount": "13.00"
    }
  }
}

Corresponding error message which will be reported back:

[
  {
    "ad_title": [
      { "#": "\"random_prop\" is not a permitted key." },
      { "#/min_length": "-3 must be greater than or equal to 0., -3 is not a null." }
    ],
    "average_daily_budget": [
      { "#": "\"currency\" wasn't supplied." }
    ]
  }
]

HTTP request

POST "#{base_endpoint}/errors"

Expected HTTP status responses:

  • 200 - OK

JSON input

{
  "request_id": "example-request-id",
  "message": "[{\"ad_title\":[{\"#\\/min_length\":\"-3 must be greater than or equal to 0., -3 is not a null.\"}]}]",
}
request_id The ID of the request whose response caused a validation failure.
message A JSON serialized string describing the errors with the app response.

JSON output

A JSON body is not required in the response.

Sign up for a Partner account to get started.

Sign up