This page documents the standard attributes in the payloads sent out by the app concerning contacts, receipts, carts and order notifications. For which Shopify customer metafields are mapped to which Customer Engagement attributes, and how you can add your own, see Mapping metafields to attributes.
Contact data mapping
| Customer Engagement | Source data (Shopify) |
|---|---|
| Contact.externalId | Customer.id |
| Contact.contactType NOTE: 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”. | “member” or “contact” |
| Contact.firstName | Customer.firstName |
| Contact.lastName | Customer.lastName |
| Contact.email | Customer.email |
| Contact.gender NOTE: Customer metafield in Shopify. Permitted values: Male, Female, Other, Prefer not to say. | voyado.gender |
| Contact.mobilePhone | Customer.phone |
| Contact.createdInStore NOTE: Based on the Store to country mapping in the Customer Engagement app in Shopify | Associated Store for the order’s shipping country |
| 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 NOTE: The lang attribute is the one populated. See Attribute mapping. | Derived from Customer.locale |
| Contact.zipCode | Customer.defaultAddress.zip |
| Contact.birthdate NOTE: Customer metafield in Shopify, type Date. See Birthday and gender data. | voyado.birthDay |
| Contact.preferences.acceptsEmail NOTE: Set on Contact creation and opt-in through an order | Customer.emailMarketingConsent.marketingState == SUBSCRIBED |
| Contact.preferences.acceptsSms NOTE: Set on Contact creation and opt-in through an order | Customer.smsMarketingConsent.marketingState == SUBSCRIBED |
| Contact.preferences.acceptsPostal NOTE: Only set on Contact creation. | false |
| Contact.consents[id=“hasShopifyAccount”].value NOTE: 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. | (no longer set) |
| Contact.consents[id=“hasShopifyAccount”].source NOTE: Deprecated, see above. | (no longer set) |
Receipt data mapping
| Customer Engagement | Source data (Shopify) - orders/create | Source data (Shopify) - refunds/create |
|---|---|---|
| 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 | |
| Receipt.currency | Order.currencyCode | Refund.order.currencyCode |
| Receipt.exchangeRateToGroupCurrency | ||
| 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 NOTE: See *A1 | Derived from type of Order.lineItems[].discountAllocations[].discountApplication | |
| Receipt.items[].discounts[].description NOTE: See *A2 | Derived from type of Order.lineItems[].discountAllocations[].discountApplication | |
| Receipt.items[].discounts[].value | Order.lineItems[].discountAllocations[].allocatedAmountSet.shopMoney.amount |
Receipt.storeExternalId: Based on the Store to country mapping in the Customer Engagement app in Shopify.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).
A1 - Discount type mapping
All Shopify discount applications map to the single Customer Engagement 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 | Customer Engagement 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 Customer Engagement, so cart tracking depends on Web pixel tracking being enabled and on the visitor having granted marketing consent.
| Customer Engagement | 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 |
Customer Engagement order notification
Order-level fields
| Customer Engagement | Source (Shopify) |
|---|---|
| contact.matchKey NOTE: The customer’s email address. | Order.email |
| contact.matchKeyType NOTE: Order notifications always match on email. Customer Engagement also supports “contactId” as a match key type, which is what receipt sync uses. | ”email” |
| 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 |
Line item fields
| Customer Engagement | Source (Shopify) - orders/* | Source (Shopify) - refunds/create |
|---|---|---|
| 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 NOTE: See *B1 | Order.lineItems[].discountAllocations[].discountApplication | |
| items[].discounts[].description NOTE: See *B2 | Order.lineItems[].discountAllocations[].discountApplication | |
| items[].discounts[].value | Order.lineItems[].discountAllocations[].allocatedAmountSet.shopMoney.amount |
B1 - Discount type mapping
As with A1, every Shopify discount application maps to the Customer Engagement discount type 235. The description (see B2) carries which kind of discount it was.B2 - Discount description mapping
| DiscountApplication type | Customer Engagement discount description |
|---|---|
| AutomaticDiscountApplication | Automatic discount: \${AutomaticDiscountApplication.code} |
| DiscountCodeApplication | Discount code: \${DiscountCodeApplication.code} |
| ManualDiscountApplication | Manual discount: \${ManualDiscountApplication.description} |
| ScriptDiscountApplication | Script discount: \${ScriptDiscountApplication.title} |
| Customer Engagement | Source (Shopify) - orders/* | Source (Shopify) - refunds/create |
|---|---|---|
| contact.matchKey NOTE: The customer’s email address. | Order.email | |
| contact.matchKeyType NOTE: Order notifications always match on email. Customer Engagement also supports “contactId” as a match key type, which is what receipt sync uses. | ”email” | |
| orderStatus NOTE: See *B3 | ||
| paymentStatus NOTE: 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 NOTE: See *B1 | Order.lineItems[].discountAllocations[].discountApplication | |
| items[].discounts[].description NOTE: See *B2 | Order.lineItems[].discountAllocations[].discountApplication | |
| items[].discounts[].value | Order.lineItems[].discountAllocations[].allocatedAmountSet.shopMoney.amount |
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” |