This page documents the payloads the app sends: contact, receipt, cart and order notification. For the configurable side — which Shopify customer metafields map to which Engage attributes, and how to add your own — see Map Shopify customer metafields to Voyado Engage attributes.
Contact data mapping
| Engage | Source data (Shopify) | Notes |
|---|---|---|
| Contact.externalId | Customer.id | |
| Contact.contactType | ”member” or “contact” | Only set on contact creation, according to the “Customer Contact Type” setting. With Shopify New customer accounts all customers are created as “member” regardless of the setting; Shopify POS customers are always “member”. |
| Contact.firstName | Customer.firstName | |
| Contact.lastName | Customer.lastName | |
| Contact.email | Customer.email | |
| Contact.gender | voyado.gender | Customer metafield in Shopify. Permitted values: Male, Female, Other, Prefer not to say. |
| Contact.mobilePhone | Customer.phone | |
| Contact.createdInStore | Associated Store for the order’s shipping country | Based on the Store to country mapping in the Engage app in Shopify |
| Contact.street | Customer.defaultAddress.address1 , Customer.defaultAddress.address2 | |
| Contact.city | Customer.defaultAddress.city | |
| Contact.country | Customer.defaultAddress.country | |
| Contact.countryCode | Customer.defaultAddress.countryCodeV2 | |
| Contact.language / Contact.lang | Derived from Customer.locale | The lang attribute is the one populated. See Attribute mapping. |
| Contact.zipCode | Customer.defaultAddress.zip | |
| Contact.birthdate | voyado.birthDay | Customer metafield in Shopify, type Date. See Birthday and gender data. |
| Contact.preferences.acceptsEmail | Customer.emailMarketingConsent.marketingState == SUBSCRIBED | Set on Contact creation and opt-in through an order |
| Contact.preferences.acceptsSms | Customer.smsMarketingConsent.marketingState == SUBSCRIBED | Set on Contact creation and opt-in through an order |
| Contact.preferences.acceptsPostal | false | Only set on Contact creation. |
| Contact.consents[id=“hasShopifyAccount”].value | (no longer set) | Deprecated. Shopify New customer accounts do not expose account status, so this consent can no longer be populated. Do not use it for contact types or segmentation. |
| Contact.consents[id=“hasShopifyAccount”].source | (no longer set) | Deprecated, see above. |
Receipt data mapping
| Engage | Source data (Shopify) - orders/create | Source data (Shopify) - refunds/create | Notes |
|---|---|---|---|
| Receipt.contact.matchKey | Associated Contact Voyado Contact ID | ||
| Receipt.contact.matchKeyType | ”contactId” | ||
| Receipt.uniqueReceiptId | Order.id | Refund.order.id | |
| Receipt.receiptNumber | Order.name | Refund.order.name | |
| Receipt.createdDate | Order.createdAt | Refund.createdAt | |
| Receipt.storeExternalId | Associated Store External ID by the order’s shipping country | Based on the Store to country mapping in the Engage app in Shopify | |
| Receipt.currency | Order.currencyCode | Refund.order.currencyCode | |
| Receipt.exchangeRateToGroupCurrency | The exchange rate between “voyado group currency” (see settings) and Order.currencyCode / Refund.order.currencyCode , retrieved from third party service (with 24-hours accuracy) | ||
| Receipt.totalGrossPrice | Order.totalPriceSet.shopMoney.amount | Refund.totalRefundedSet.shopMoney.amount | |
| Receipt.paymentMethods[].type | Order.transactions[].gateway | Refund.transactions[].gateway | |
| Receipt.paymentMethods].description | Order.transactions[].[paymentProvider.name | Refund.transactions].[paymentProvider.name | |
| Receipt.paymentMethods[].value | Order.transactions[].amountSet.shopMoney.amount | Refund.transactions[].amountSet.shopMoney.amount | |
| Receipt.items[] | Order.lineItems[] | Refund.refundLineItems[] | |
| Receipt.items[].type | ”PURCHASE" | "RETURN” | |
| Receipt.items[].quantity | Order.lineItems[].quantity | Refund.refundLineItems[].quantity | |
| Receipt.items[].grossPaidPrice | Order.lineItems[].discountedTotalSet.shopMoney.amount | Refund.refundLineItems[].priceSet.shopMoney.amount | |
| Receipt.items[].taxAmount | Sum of Order.lineItems[].taxLines[*].priceSet.shopMoney.amount | Refund.refundLineItems[].totalTaxSet.shopMoney.amount | |
| Receipt.items[].sku | Order.lineItems[].sku | Refund.refundLineItems[].lineItem.sku | |
| Receipt.items[].articleNumber | Order.lineItems[].sku | Refund.refundLineItems[].lineItem.sku | |
| Receipt.items[].discounts[].type | Derived from type of Order.lineItems[].discountAllocations[].discountApplication | See *A1 | |
| Receipt.items[].discounts[].description | Derived from type of Order.lineItems[].discountAllocations[].discountApplication | See *A2 | |
| Receipt.items[].discounts[].value | Order.lineItems[].discountAllocations[].allocatedAmountSet.shopMoney.amount |
A1 - Discount type mapping
All Shopify discount applications map to the single Engage discount type 235, which represents a Shopify discount. This applies toAutomaticDiscountApplication, DiscountCodeApplication, ManualDiscountApplication and ScriptDiscountApplication alike — the distinction between them is carried in the description instead (see A2).
A2 - Discount description mapping
| DiscountApplication type | Engage discount description |
|---|---|
| AutomaticDiscountApplication | Automatic discount: \${AutomaticDiscountApplication.code} |
| DiscountCodeApplication | Discount code: \${DiscountCodeApplication.code} |
| ManualDiscountApplication | Manual discount: \${ManualDiscountApplication.description} |
| ScriptDiscountApplication | Script discount: \${ScriptDiscountApplication.title} |
Setting sale prices for products in Shopify is not considered a discount. Using sale prices is different from setting up discounts. Compare-at price is not considered a discount and will not be mapped as a discount in the data sync to engage. (https://help.shopify.com/en/manual/discounts)
Cart abandonment
Cart data is not fetched from Shopify’s checkout APIs. The payload is published client-side by the web pixel and sent directly to Engage, so cart tracking depends on Web pixel tracking being enabled and on the visitor having granted marketing consent.
| Engage | Source |
|---|---|
| [].CartReference | Cart identifier from the web pixel event |
| [].Time | Timestamp of the cart event |
| [].ContactId | Identified contact ID |
| [].Language | Locale of the storefront the visitor is browsing |
| [].Url | Checkout URL for the abandoned cart |
| [].Items[].Sku | SKU of each line in the cart |
| [].Items[].Quantity | Quantity of each line in the cart |
Engage order notification
| Engage | Source (Shopify) - orders/* | Source (Shopify) - refunds/create | Notes |
|---|---|---|---|
| contact.matchKey | Order.email | The customer’s email address. | |
| contact.matchKeyType | ”email” | Order notifications always match on email. Engage also supports “contactId” as a match key type, which is what receipt sync uses. | |
| orderStatus | See *B3 | ||
| paymentStatus | See *B4 | ||
| orderNumber | Order.name | ||
| createdDate | Order.createdAt | ||
| shippingDate | Order.fulfillments[0]?.createdAt | ||
| statusChangedDate | Order.updatedAt | ||
| storeId | Associated store ID for the order’s shipping country (see Store to country mapping.) | ||
| currency | Order.currencyCode | ||
| language | Order.customerLocale.split(’-’)[0] | ||
| totalGrossPrice | Order.totalPriceSet.shopMoney.amount | ||
| freightFee.value | Order.shippingLine.discountedPriceSet.shopMoney.amount | ||
| freightFee.tax | sum(Order.shippingLine.taxLines[].priceSet.shopMoney.amount | ||
| totalTax | Order.totalTaxSet.shopMoney.amount | ||
| taxDetails[].description | Order.taxLines[].title | ||
| taxDetails[].value | Order.taxLines[].priceSet.shopMoney.amount | ||
| extraData.CreatedDate | Order.createdAt | ||
| extraData.ShippingDate | Order.fulfillments[0]?.createdAt | ||
| extraData.ReturnDate | Order.refunds[0]?.createdAt | ||
| extraData.TrackingNumber | Order.fulfillments?[0].trackingInfo.number | ||
| extraData.TrackingLinkHTML | Order.fulfillments?[0].trackingInfo.url | ||
| extraData.ShippingMethod | Order.shippingLine.code | ||
| extraData.ShippingAddressFirstName | Order.shippingAddress.firstName | ||
| extraData.ShippingAddressLastName | Order.shippingAddress.lastName | ||
| extraData.ShippingAddressStreet | Order.shippingAddress.address1 + Order.shippingAddress.address2 | ||
| extraData.ShippingAddressZipCode | Order.shippingAddress.zip | ||
| extraData.ShippingAddressCity | Order.shippingAddress.city | ||
| extraData.ShippingAddressCountry | Order.shippingAddress.countryCodeV2 | ||
| extraData.ShippingAddressPhone | Order.shippingAddress.phone | ||
| extraData.ShippingAddressEmail | Order.email | ||
| extraData.PaymentMethod | Order.paymentGatewayNames.join(’,‘) | ||
| extraData.BillingAddressFirstName | Order.billingAddress.firstName | ||
| extraData.BillingAddressLastName | Order.billingAddress.lastName | ||
| extraData.BillingAddressStreet | Order.billingAddress.address1 + Order.billingAddress.address2 | ||
| extraData.BillingAddressZipCode | Order.billingAddress.zip | ||
| extraData.BillingAddressCity | Order.billingAddress.city | ||
| extraData.BillingAddressCountry | Order.billingAddress.countryCodeV2 | ||
| extraData.BillingAddressPhone | Order.billingAddress.phone | ||
| extraData.BillingAddressEmail | Order.email | ||
| items[] | Order.lineItems[LineItem.refundableQuantity > 0] | Order.refunds[].refundLineItems[] | |
| items[].type | ”purchase" | "return” | |
| items[].sku | Order.lineItems[].sku | Order.refunds[].refundLineItems[].sku | |
| items[].quantity | Order.lineItems[].refundableQuantity | Order.refunds[].refundLineItems[].quantity | |
| items[].grossPaidPrice | Order.lineItems[].discountedTotalPriceSet.shopMoney.amount | Order.refunds[].refundLineItems[].priceSet.shopMoney.amount | |
| items[].taxAmount | sum(Order.lineItems[].taxLines[].priceSet.shopMoney.amount) | Order.refunds[].refundLineItems[].totalTaxAmount.shopMoney.amount | |
| items[].description | Order.lineItems[].name | ||
| items[].imageUrl | Order.lineItems[].image.src | ||
| items[].discounts[].type | Order.lineItems[].discountAllocations[].discountApplication | See *B1 | |
| items[].discounts[].description | Order.lineItems[].discountAllocations[].discountApplication | See *B2 | |
| items[].discounts[].value | Order.lineItems[].discountAllocations[].allocatedAmountSet.shopMoney.amount |
B1 - Discount type mapping
As with A1, every Shopify discount application maps to the Engage discount type 235. The description (see B2) carries which kind of discount it was.B2 - Discount description mapping
| DiscountApplication type | Engage discount description |
|---|---|
| AutomaticDiscountApplication | Automatic discount: \${AutomaticDiscountApplication.code} |
| DiscountCodeApplication | Discount code: \${DiscountCodeApplication.code} |
| ManualDiscountApplication | Manual discount: \${ManualDiscountApplication.description} |
| ScriptDiscountApplication | Script discount: \${ScriptDiscountApplication.title} |
B3 - Order status mapping
| Condition | Value |
|---|---|
| Order.refunds | ”REFUNDED” |
| Order.fulfillments | ”FULFILLED” |
| Order.partialFulfillments | ”PARTIALLY_FULFILLED” |
| Order.cancellations | ”CANCELLED” |
| Otherwise | ”CONFIRMED” |
Orders originating in Shopify POS carry a
POS prefix on the status value, for example POS_CONFIRMED.B4 - Order payment status mapping
| Condition | Value |
|---|---|
| Order.fullyPaid | ”PAID” |
| Order.unpaid | ”UNPAID” |
| Otherwise | ”PARTIALLY_PAID” |