> ## Documentation Index
> Fetch the complete documentation index at: https://developer.voyado.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Promotions

Rewarding your customers always pays off. For this, Customer Engagement has a Promotions module.

There are several kinds of promotions available in Customer Engagement but the kind you can interact with via the Customer Engagement API are *multichannel promotions* so these are the main focus here.

## Multichannel promotions

A multichannel promotion is one that can be set up to be used in-store or online or both ways. These promotions can be assigned to a contact based on their previous purchases or on any other type of contact data or interaction.

You can assign multichannel promotions to segments as well as to single contacts in Customer Engagement. For this reason they're also known as *personal offers*.

Multichannel promotions can be used:

* **In-store** - The customer is identified at the POS and their active multichannel promotions are fetched from Customer Engagement, ready to use in the checkout

* **Online** - The customer will need to identify themselves either by logging in, or when they fill in their details at the checkout. Then all available multichannel promotions can be fetched from Customer Engagement and shown. How exactly this is done depends on how the e-com has been set up

## Working with multichannel promotions

There are several ways to create and assign multichannel promotions when working with Customer Engagement. In this article the focus will be mostly *on the Customer Engagement API* but there are also these methods:

<AccordionGroup>
  <Accordion title="Promotions via the Customer Engagement UI">
    <Frame hint="Setting up a multichannel promotion in Customer Engagement">
      <img src="https://mintcdn.com/voyado/hR3XnwpVM3bU1hec/images/promotions01.png?fit=max&auto=format&n=hR3XnwpVM3bU1hec&q=85&s=8beb4891821bf4d6a5569031894b9c7c" alt="Setting up a multichannel promotion in Customer Engagement " width="704" height="709" data-path="images/promotions01.png" />
    </Frame>

    Select the *type* of the promotion from the dropdown. You can use different values for "Can be used in store" and "Can be used online" in the same promotion. The options are:

    * Monetary discount
    * Discount in percent
    * Manual instruction (only for in-store POS)
    * External promotion code

    In the value type "External promotion code" the actual price rule is not stored in the promotion itself but in the retailer's system (ERP or e-com platform). When this kind of promotion is displayed, the POS looks up the relevant price rule using the external promotion code and then displays those details. Staff or customers will not notice this happening.

    <Tip>
      Whenever a new promotion is created via the Customer Engagement UI, it is a draft state amd must be manually activated before it can be assigned to contacts.
    </Tip>

    <Card title="Learn about setting up multichannel promotions" href="https://help.engage.voyado.com/hc/en-gb/articles/17234914361500-Set-up-a-multichannel-promotion" icon="https://mintcdn.com/voyado/Ns4bBcK3LNctK_Un/icons/help-center-link.png?fit=max&auto=format&n=Ns4bBcK3LNctK_Un&q=85&s=3e7ca2ce0b8cfb9fbd27a4bdf53b2ce1" horizontal width="128" height="128" data-path="icons/help-center-link.png" />
  </Accordion>

  <Accordion title="Promotions via XML file import">
    Assigning promotions by XML file import are covered in this article:

    <Card title="Assigning promotions by XML file" href="/docs/file-based-transfer/promotions-xml-create-assign" icon="https://mintcdn.com/voyado/Ns4bBcK3LNctK_Un/icons/developer-link.png?fit=max&auto=format&n=Ns4bBcK3LNctK_Un&q=85&s=fbd08f956358ab12f664a7158e1a1399" horizontal width="128" height="128" data-path="icons/developer-link.png" />
  </Accordion>
</AccordionGroup>

<Warning>
  The rest of this article will focus on working with promotions using the Customer Engagement API.
</Warning>

## Get promotions for contact

The API endpoint used to fetch all available promotions assigned to a contact is:

```http theme={null}
GET /api/v3/promotion-assignments
```

These query string parameters are used:

* **contactId** - The contact's unique ID. If you have some other contact identifier, use [this endpoint](/docs/contacts/identify-contact/find-contact)  to get the `contactId` you will need for this request.

* **redemptionChannelType** - "ECOM", "POS" or "OTHER". If left out, all promotions are returned.

<Accordion title="See example request">
  ```http theme={null}
  GET /api/v3/promotion-assignments?contactId=67ea5555-0e09-484f-b4c4-aa3e00e1e24c&redemptionChannelType=ECOM
  ```
</Accordion>

<Accordion title="See example response">
  ```json theme={null}

  [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "promotionId": "00000000-0000-0000-0000-000000000000",
      "externalId": "string",
      "type": "string",
      "name": "string",
      "expiresOn": "2026-04-24T10:44:50.626Z",
      "validFrom": "2026-04-24T10:44:50.626Z",
      "heading": "string",
      "description": "string",
      "redeemed": true,
      "redeemedOn": "2026-04-24T10:44:50.626Z",
      "assignedOn": "2026-04-24T10:44:50.626Z",
      "source": "Automation",
      "imageUrl": "string",
      "link": "string",
      "redemptionChannels": [
        {
          "type": "POS",
          "valueType": "PERCENT",
          "value": 10,
          "localValues": [
            {
              "amount": 123.45,
              "currency": "EUR"
            }
          ],
          "instruction": "string"
        }
      ]
    }
  ]
  ```

  <ResponseField name="id" type="string">
    This identifies a specific "instance" of the promotion that was created from the "template" that was identified by `promotionId`. The `id` value is unique to that contact and is the one used when redeeming this promotion.
  </ResponseField>

  <ResponseField name="promotionId" type="string">
    This is the ID of the main promotion (the "template" this particular promotion was created from). Many customers can have the same value for this, since it just refers to the main promotion. This CAN'T be used to redeem a promotion.
  </ResponseField>

  <ResponseField name="externalId" type="string">
    This value exists when the promotion has been created over the API. If the promotion was created manually in the Customer Engagement UI, this is null. It is not important when redeeming the promotion.
  </ResponseField>

  <Warning>
    The value returned as `id` is what you must use to redeem the promotion.
  </Warning>
</Accordion>

<Warning>
  A request to this endpoint will only return those promotions where the assigned promotion AND the main promotion are active, and with a start date that is *not* in the future. This query will include already redeemed promotions as long as they otherwise fulfill the criteria for being active.
</Warning>

### Redemption channels

When getting a contact's promotion data, you'll get back something like this:

```json theme={null}
[
  {
    "id": "bb247ef7-80bb-440c-beb7-ab4172e56666",
    "promotionId": "1a50b7aa-d697-4dc6-3ce7-ab2c00b3a920",
    "externalId": "SO-14034",
    "type": "Multichannel",
     ...
    "redemptionChannels": [
      {
        "type": "POS",
        "valueType": "EXTERNALOFFER",
        "value": "10056",
        "instruction": null
      },
      {
        "type": "ECOM",
        "valueType": "EXTERNALOFFER",
        "value": "10056",
        "instruction": null
      }
    ]
  }
]
```

The `redemptionChannels` object in the response specifies if a promotion is a percentage, a monetary amount to be withdrawn from the total amount of the transaction, or an external discount code that refers to a price rule in your platform.

<Accordion title="See redemption channels object">
  ```json theme={null}
  [
    {
      ...
      "redemptionChannels": [
        {
          "type": "POS",
          "valueType": "MONEY",
          "value": {
              "currency": "EUR",
              "amount": 10
          },
          "instruction": null
        }
      ]
    }
    ...
  ]
  ```
</Accordion>

These are the fields in the `redemptionChannels` object:

* **type** - Which channel the promotion applies to.

* **valueType** - Which type of discount we want to assign (percentage, monetary or external code).

* **value** - The percentage, monetary amount or external code referring to the external price rule. With a monetary discount we also expose the *group currency* set in Customer Engagement for that client.

* **instruction** - An instruction that can be used, for example, to give some information to store staff regarding the promotion.

<Tip>
  These can be displayed in all currencies declared in your Customer Engagement environment.
</Tip>

Some `redemptionChannels` examples:

<AccordionGroup>
  <Accordion title="Example: POS promotion with percentage discount">
    ```json theme={null}
    "redemptionChannels": [
        {
            "type": "POS",
            "valueType": "PERCENT",
            "value": 15,
            "instruction": "POS instructions."
        }
    ]
    ```
  </Accordion>

  <Accordion title="Example: E-com promotion with external discount code">
    ```json theme={null}
    "redemptionChannels": [
        {
            "type": "ECOM",
            "valueType": "EXTERNALOFFER",
            "value": "Mega_discount_March25",
            "instruction": "POS instructions."
        }
    ]
    ```
  </Accordion>

  <Accordion title="Example: POS promotion with monetary discount">
    ```json theme={null}
    "redemptionChannels": [
        {
            "type": "POS",
            "valueType": "MONEY",
            "value": {
                "currency": "EUR",
                "amount": 10
            }
        }    
    ]
    ```
  </Accordion>
</AccordionGroup>

## Create a promotion

Create a promotion using the following endpoint:

```http theme={null}
POST /api/v3/promotions/multichannels
```

When creating a promotion you can give it either:

* A start date and a validity time
* A start date and an end date

<Warning>
  When sending in the `startDate` or `endDate` send only the year, month and day as in the examples shown below. Specific times in a day cannot be used.
</Warning>

<AccordionGroup>
  <Accordion title="Creation request - start date and validity time">
    ```json theme={null}
    {
      "externalId": "externalIdentifier",
      "name": "Example promotion name",
      "validity": {
        "startDate": "2020-03-18",
        "assignedValidity": {
          "unit": "Months",
          "amount": 3
        }
      },
      "presentation": {
        "heading": "Example heading",
        "description": "Example description",
        "link": "http://www.example.com"
      },
      "redemptionChannels": [
        {
          "type": "POS",
          "valueType": "EXTERNALOFFER",
          "value": "POS-CAMPAIGN-123",
          "instruction": "Example POS instruction"
        },
        {
          "type": "ECOM",
          "valueType": "EXTERNALOFFER",
          "value": "ECOM-CAMPAIGN-1337"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Creation response - start date and validity time">
    Here is a successful response, containing the promotion's ID:

    ```json theme={null}
    {
      "id": "9125f018-1838-4f65-929d-87f0b4353661",
      "status": "Draft",
      "externalId": "externalIdentifier",
      "name": "Example promotion ame",
      "validity": {
        "startDate": "2020-03-18T00:00:00+02:00",
        "assignedValidity": {
          "unit": "Months",
          "amount": 3
        }
      },
     "presentation": {
        "heading": "Example heading",
        "description": "Example description",
        "link": "http://www.example.com"
      },
      "redemptionChannels": [
        {
          "type": "POS",
          "valueType": "EXTERNALOFFER",
          "value": "POS-CAMPAIGN-123",
          "instruction": "Example POS instruction"
        },
        {
          "type": "ECOM",
          "valueType": "EXTERNALOFFER",
          "value": "ECOM-CAMPAIGN-1337"
        }
      ]
    }
    ```

    <Warning>
      The value id here is the ID for the promotion definition. This is used to assign this promotion to individual contacts. Each assignment gets its own ID, connecting this promotion and contact.
    </Warning>
  </Accordion>
</AccordionGroup>

<AccordionGroup>
  <Accordion title="Creation request - start date and end date">
    Here is what such a request looks like:

    ```json theme={null}
    {
      "externalId": "externalIdentifier",
      "name": "Example promotion name",
      "validity": {
        "startDate": "2020-03-18",
        "endDate": "2020-06-18",
      },
      "presentation": {
        "heading": "Example heading",
        "description": "Example description",
        "link": "http://www.example.com"
      },
      "redemptionChannels": [
        {
          "type": "POS",
          "valueType": "EXTERNALOFFER",
          "value": "POS-CAMPAIGN-123",
          "instruction": "Example POS instruction"
        },
        {
          "type": "ECOM",
          "valueType": "EXTERNALOFFER",
          "value": "ECOM-CAMPAIGN-1337"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Creation response - start date and end date">
    Here is a successful response, containing the promotion's ID:

    ```json theme={null}
    {
      "id": "9125f018-1838-4f65-929d-87f0b4353661",
      "status": "Draft",
      "externalId": "externalIdentifier",
      "name": "Promotion Name",
      "validity": {
        "startDate": "2020-03-18T00:00:00+02:00",
        "endDate": "2020-06-18T23:59:59+02:00",
        "assignedValidity": null
      },
      "presentation": {
        "heading": "Promotion presentation title",
        "description": "Promotion presentation description",
        "link": "http://www.example.com"
      },
      "redemptionChannels": [
        {
          "type": "POS",
          "valueType": "EXTERNALOFFER",
          "value": "POS-CAMPAIGN-123"
        },
        {
          "type": "ECOM",
          "valueType": "EXTERNALOFFER",
          "value": "ECOM-CAMPAIGN-1337"
        }
      ]
    }
    ```

    The maximum value for end date is "9999-12-30".
  </Accordion>
</AccordionGroup>

If your creation request has been successful, you'll get a *HTTP 200 OK* response.

Otherwise you'll get one of these:

* *400: BadRequest*
* *500: Error*

<Tip>
  As can be seen in the responses above, a promotion is created with status "Draft". Assigning the promotion via the API will automatically activate it. The creation response also contains the promotion `id` value which is needed when assigning the promotion to a contact (along with the contact ID).
</Tip>

## Update a promotion

You can update (alter) a multichannel promotions over the Customer Engagement API.

<Warning>
  Only *draft* promotions can be updated, not active promotions.
</Warning>

You can update the entire promotion or only its validity. Each method uses a different API endpoint:

<AccordionGroup>
  <Accordion title="Updating the entire promotion">
    To update the entire multichannel promotion, used this endpoint:

    ```http theme={null}
    PUT /api/v3/promotions/multichannels/{id}
    ```

    Send a payload in the same format as when you created the promotion, including your updates:

    ```json theme={null}
    {
      "id": "24138ab3-0421-4688-a4b0-40a3c410fa4f",
      "status": "Draft",
      "externalId": "externalIdentifier",
      "name": "Promotion Name",
      "validity": {
        "startDate": "2019-10-15T11:18:50.5258317+02:00",
        "assignedValidity": {
          "unit": "Months",
          "amount": 3
        }
      },
      "presentation": {
        "heading": "Promotion presentation title",
        "description": "Promotion presentation description",
        "link": "http://www.example.com"
      },
      "redemptionChannels": [
        {
          "type": "POS",
          "valueType": "EXTERNALOFFER",
          "value": "PromotionCode123",
        },
        {
          "type": "ECOM",
          "valueType": "EXTERNALOFFER",
          "value": "PromotionCode123"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Updating the promotion's validity">
    To update just the duration of an existing multichannel promotion, send a PUT to this endpoint:

    ```http theme={null}
    PUT /api/v3/promotions/multichannels/{id}/validity
    ```

    With a payload formatted like this:

    ```json theme={null}
    {
      "startDate": "2019-10-14T09:18:50.885+01:00",
      "endDate": "2021-10-14T09:18:50.885+01:00",
      "assignedValidity": {
        "unit": "Months",
        "amount": 4
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Assign a promotion

A promotion is of no use unless it is assigned to contacts. Multichannel promotions are assigned to contacts via the API like this:

```http theme={null}
POST /api/v3/promotion-assignments
```

With this request you send the following kind of payload:

```json theme={null}
{
    "contactId": "e0e63d15-1881-4f9a-9ce1-94c3e21c6f47",
    "promotionId": "132aa326-fa89-4a8b-af9f-0b232157f8a2"
}
```

<Warning>
  The `promotionId` value used here to assign the promotion is the `id` value returned when the promotion was created.
</Warning>

If your request has been successful, you'll get a *HTTP 200 OK* response.

Otherwise you'll get one of these:

* *400: InvalidContactId, InvalidPromotionId*
* *404: ContactNotFound, PromotionNotFound*

## Redeem a promotion

As soon as a promotion is used in a purchase, it must be redeemed in Customer Engagement so it can't be used again. To do this you'll need to know:

* the promotion's unique assignment ID
* the redemption channel, either "POS" or "ECOM"

<Tip>
  Since the assignment ID is already connected to a unique contact, the contact's details are not needed when redeeming a promotion.
</Tip>

There are two steps in redeeming:

<Steps>
  <Step title="Call the /redeem endpoint">
    Redeem the promotion with a call to this endpoint, adding the assignment ID in the path:

    ```http theme={null}
    POST /api/v3/promotion-assignments/{id}/redeem
    ```

    In the request body, add the redemption channel:

    ```json theme={null}
    {  
       "redemptionChannel": "ECOM" 
    }
    ```
  </Step>

  <Step title="Add promotion ID to /receipts call">
    As with every purchase, you will also call this endpoint with the full receipt information:

    ```http theme={null}
    POST /api/v3/receipts
    ```

    Here you will add the assignment ID of the promotion used to the `usedPromotions` block in the body of the query:

    ```json theme={null}
    ...
    "usedPromotions": [
        {
            "promotionId": "6374dfaa-31c5-4e34-bb88-7ccfc5fe8bf7"
        }
    ]
    ...
    ```
  </Step>
</Steps>

If you only call the `/receipts` endpoint but not `/redeem`, the promotion will still be redeemed, but often after a delay, bringing the risk that it could be used again.

If you call `/redeem` but miss to add the promotion assignment ID to the `/receipts` call, the system will not know which transaction to connect the redeemed promotion to. It will try to work it out based on the timestamp, checking for transactions within ±5 minutes of the redemption.

So you should always do both of these things whenever a promotion is used in a purchase.

<Warning>
  After 4.5 seconds the `/redeem` endpoint will time out, provided it has not already saved the promotion as redeemed. If the promotion *has* been redeemed (this is known as the "point of no return") when the 4.5 seconds limit is passed, the request will be processed until completion. It is therefore recommended to not have a timeout *shorter than 60 seconds*, in order to avoid situations where a promotion is redeemed in Customer Engagement but the calling system does not know it due to aborting the request.
</Warning>

### Redemption response codes

If the request has been successful, you'll get a *HTTP 200 OK* response.

Otherwise you'll get one of these:

* *400: NotAValidRedemptionChannel, InvalidPromotionIdNoData*
* *404: PromotionNotFound, PromotionNotAssociatedWithContact*
* *409: PromotionNotValidForRedemptionChannel, PromotionAlreadyRedeemed, PromotionAlreadyRedeemed*
* *504: GatewayTimeout*

## Delete a promotion

You can delete a promotion using its Customer Engagement ID or its external ID (assuming an external ID was used when creating the promotion).

<Warning>
  Only *unassigned* promotions can be deleted.
</Warning>

<AccordionGroup>
  <Accordion title="Delete a promotion using its Customer Engagement ID">
    To delete a multichannel promotion using the ID, use this endpoint:

    ```http theme={null}
    DELETE /api/v3/promotions/multichannels/{id}
    ```

    The `id` value here is the Customer Engagement ID ("31bc4f89-6e71-4086-8627-af8f01073331" or similar).
  </Accordion>

  <Accordion title="Delete a promotion using its external ID">
    To delete a multichannel promotion using the external ID, use this endpoint with `externalId` as the query parameter:

    ```http theme={null}
    DELETE /api/v3/promotions/multichannels?externalId=whatever
    ```
  </Accordion>
</AccordionGroup>

## Export promotions by XML

Exporting promotions by XML files are covered in this article:

<Card title="Exporting promotions by XML file" href="/docs/file-based-transfer/promotions-xml-export" icon="https://mintcdn.com/voyado/Ns4bBcK3LNctK_Un/icons/developer-link.png?fit=max&auto=format&n=Ns4bBcK3LNctK_Un&q=85&s=fbd08f956358ab12f664a7158e1a1399" horizontal width="128" height="128" data-path="icons/developer-link.png" />


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.