Skip to main content
This page is the authoritative payload reference for every webhook Galantis registers with Shopify and every webhook event it receives from Meta. It covers the topic, the expected payload structure, and what the webhook drives in Galantis. For the processing behavior and downstream effects of each webhook, see Integrations — Shopify Webhooks and Integrations — Meta Webhooks. This page focuses on payload structure for developer reference.

Security

Shopify webhooks — All incoming Shopify webhooks are validated via HMAC signature verification using the X-Shopify-Hmac-Sha256 header before processing. Requests with invalid or missing signatures are rejected with a 401 response and never reach the handler. Meta webhooks — All incoming Meta webhooks are validated via signature verification using the X-Hub-Signature-256 header before processing. Invalid signatures are rejected. Both verifications use the app secret for their respective platform. Webhook payloads are never processed without a valid signature.

Shopify webhooks

Customer webhooks

customers/create
{
  "id": 123456789,
  "email": "customer@example.com",
  "first_name": "Jane",
  "last_name": "Smith",
  "phone": "+521234567890",
  "tags": "vip, newsletter",
  "accepts_marketing": true,
  "email_marketing_consent": {
    "state": "subscribed",
    "opt_in_level": "single_opt_in"
  },
  "sms_marketing_consent": {
    "state": "subscribed",
    "opt_in_level": "single_opt_in"
  },
  "locale": "es",
  "created_at": "2025-01-15T10:00:00-05:00",
  "updated_at": "2025-01-15T10:00:00-05:00"
}

customers/update Same structure as customers/create. All fields are included in the payload — Galantis diffs the incoming data against the stored record to identify changes.
customers/delete
{
  "id": 123456789
}
Only the customer ID is included. Galantis uses the ID to locate and remove the corresponding contact record.
customers/marketing_consent_updated
{
  "id": 123456789,
  "email_marketing_consent": {
    "state": "subscribed",
    "opt_in_level": "single_opt_in",
    "consent_updated_at": "2025-01-15T10:05:00-05:00"
  },
  "sms_marketing_consent": {
    "state": "subscribed",
    "opt_in_level": "single_opt_in",
    "consent_updated_at": "2025-01-15T10:05:00-05:00"
  },
  "phone": "+521234567890",
  "updated_at": "2025-01-15T10:05:00-05:00"
}
Galantis maps the SMS/phone marketing consent state to the corresponding internal consent status on the contact record.
customer_tags/added
{
  "customer_id": 123456789,
  "tags_added": ["vip", "loyalty-gold"]
}

customer_tags/removed
{
  "customer_id": 123456789,
  "tags_removed": ["loyalty-silver"]
}

Order webhooks

orders/create
{
  "id": 987654321,
  "order_number": 1042,
  "customer": {
    "id": 123456789,
    "phone": "+521234567890"
  },
  "total_price": "149.00",
  "currency": "MXN",
  "line_items": [
    {
      "id": 111111,
      "title": "Running Shoes",
      "quantity": 1,
      "price": "149.00",
      "product_id": 555555,
      "variant_id": 666666,
      "vendor": "NikeMX",
      "properties": []
    }
  ],
  "tags": "",
  "fulfillment_status": null,
  "financial_status": "paid",
  "created_at": "2025-01-15T11:00:00-05:00"
}

orders/cancelled Same structure as orders/create with cancelled_at and cancel_reason fields added:
{
  "id": 987654321,
  "order_number": 1042,
  "customer": { "id": 123456789 },
  "total_price": "149.00",
  "cancelled_at": "2025-01-16T09:00:00-05:00",
  "cancel_reason": "customer"
}

orders/updated (used for shipping)
{
  "id": 987654321,
  "order_number": 1042,
  "customer": { "id": 123456789 },
  "fulfillment_status": "fulfilled",
  "fulfillments": [
    {
      "id": 222222,
      "status": "success",
      "tracking_company": "DHL",
      "tracking_number": "1234567890",
      "tracking_url": "https://track.dhl.com/...",
      "created_at": "2025-01-16T14:00:00-05:00"
    }
  ]
}
Galantis interprets orders/updated payloads to drive the relevant automation triggers (for example, ORDER_SHIPPED when fulfillment data indicates a successful shipment). See Automations — triggers for the available triggers.

Product and collection webhooks

products/create
{
  "id": 555555,
  "title": "Running Shoes",
  "body_html": "<p>Full product description...</p>",
  "vendor": "NikeMX",
  "tags": "shoes, running, sport",
  "images": [
    { "id": 777777, "src": "https://cdn.shopify.com/...", "width": 1000, "height": 1000 }
  ],
  "variants": [
    {
      "id": 666666,
      "title": "Size 42 / Blue",
      "price": "149.00",
      "compare_at_price": "180.00",
      "sku": "RUN-42-BLU",
      "inventory_quantity": 15,
      "option1": "42",
      "option2": "Blue",
      "image_id": 777777
    }
  ],
  "options": [
    { "name": "Size", "values": ["40", "41", "42", "43"] },
    { "name": "Color", "values": ["Blue", "Black", "White"] }
  ],
  "status": "active",
  "created_at": "2025-01-15T09:00:00-05:00"
}

products/update Same structure as products/create. Galantis processes the full payload — updated fields overwrite stored values, and inventory_quantity changes are checked for the 0→>0 Back-in-Stock restock pattern.
products/delete
{
  "id": 555555
}

collections/create, collections/update
{
  "id": 888888,
  "title": "Running Gear",
  "handle": "running-gear",
  "products_count": 24,
  "updated_at": "2025-01-15T12:00:00-05:00"
}

collections/delete
{
  "id": 888888
}

Billing and app lifecycle webhooks

app_subscriptions/update
{
  "app_subscription": {
    "admin_graphql_api_id": "gid://shopify/AppSubscription/123",
    "name": "Galantis Pro Plan",
    "status": "ACTIVE",
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-15T00:00:00Z",
    "currency": "USD"
  }
}

app/uninstalled Handler: Tenant deactivation
{
  "id": 12345678,
  "myshopify_domain": "your-store.myshopify.com"
}

GDPR webhooks

customers/redact
{
  "shop_id": 12345678,
  "shop_domain": "your-store.myshopify.com",
  "customer": {
    "id": 123456789,
    "email": "customer@example.com",
    "phone": "+521234567890"
  },
  "orders_to_redact": [987654321, 987654322]
}

shop/redact
{
  "shop_id": 12345678,
  "shop_domain": "your-store.myshopify.com"
}

Meta webhooks

messages (inbound)
{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "{waba_id}",
    "changes": [{
      "value": {
        "messaging_product": "whatsapp",
        "metadata": {
          "display_phone_number": "521234567890",
          "phone_number_id": "{phone_number_id}"
        },
        "contacts": [{
          "profile": { "name": "Jane Smith" },
          "wa_id": "521234567890"
        }],
        "messages": [{
          "from": "521234567890",
          "id": "wamid.{message_id}",
          "timestamp": "1705320000",
          "type": "text",
          "text": { "body": "Hello, I have a question about my order" }
        }]
      },
      "field": "messages"
    }]
  }]
}
For QUICK_REPLY button responses, the messages[0].type is "interactive" and the payload includes:
"interactive": {
  "type": "button_reply",
  "button_reply": {
    "id": "{button_id}",
    "title": "{button_label}"
  }
}

message_status (status update)
{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "{waba_id}",
    "changes": [{
      "value": {
        "messaging_product": "whatsapp",
        "metadata": {
          "display_phone_number": "521234567890",
          "phone_number_id": "{phone_number_id}"
        },
        "statuses": [{
          "id": "wamid.{message_id}",
          "status": "delivered",
          "timestamp": "1705320030",
          "recipient_id": "521234567890"
        }]
      },
      "field": "messages"
    }]
  }]
}
"status" values: "sent", "delivered", "read", "played", "failed". For "failed" status, an "errors" array is included:
"errors": [{
  "code": 131047,
  "title": "Re-engagement message",
  "message": "Message failed to send because more than 24 hours have passed since the customer last replied to this number.",
  "error_data": { "details": "..." }
}]

message_template_status_update
{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "{waba_id}",
    "changes": [{
      "value": {
        "event": "APPROVED",
        "message_template_id": 123456,
        "message_template_name": "order_confirmation_es",
        "message_template_language": "es",
        "reason": null
      },
      "field": "message_template_status_update"
    }]
  }]
}
"event" values: "APPROVED", "REJECTED", "PAUSED", "DISABLED". For "REJECTED", the "reason" field contains Meta’s rejection explanation.