Skip to content
ShopAssist Docs
Handoff

Webhook handoff

Send handoffs to your own system through the handoff.requested webhook.

Growth and up

The webhook target is a notification target: it lets your own systems react to handoffs, for example to open a ticket in a tool ShopAssist does not integrate with.

Set up

  1. Create a webhook endpoint under Developers → Webhooks and subscribe it to handoff.requested (and handoff.updated if you want state changes).
  2. Under Handoff → Routing, add Webhook as a target.

Sent with ShopAssist-Event-Type: handoff.requested. Acknowledge with any 2xx within 10 seconds.

Payload of handoff.requested
FieldTypeDescription
api_versionrequired"v1"default "v1"
created_atrequiredstringformat date-time
datarequiredHandoffRequestedData`{ handoff; conversation; transcript (≤ 50); dashboard_url }` (`handoff.shopper` carries `identity_level` and `email_verified`).
data.conversationrequiredApiConversation`ConversationSummary` without `last_message_preview`, plus `platform_customer_id`.
data.conversation.assigned_user_idrequiredstring | nullpattern `^usr_[0-9a-hjkmnp-tv-z]{26}$`
data.conversation.channelrequired"widget" | "playground" | "preview" | "demo" | "api"
data.conversation.closed_atrequiredstring | nullformat date-time
data.conversation.csat_scorerequiredinteger | null
data.conversation.handoff_requested_atrequiredstring | nullformat date-time
data.conversation.handoff_staterequired"none" | "requested" | "live" | "delivered_offline" | "failed" | "resolved"
data.conversation.idrequiredstringpattern `^conv_[0-9a-hjkmnp-tv-z]{26}$`
data.conversation.identity_levelrequired"guest" | "order_lookup" | "order_otp" | "customer"
data.conversation.is_testrequiredboolean
data.conversation.last_message_atrequiredstring | nullformat date-time
data.conversation.localerequiredstring | null
data.conversation.message_countrequiredinteger≥ 0
data.conversation.moderequired"ai" | "human_requested" | "human"
data.conversation.platform_customer_idrequiredstring | nullOnly for identity_level = customer
data.conversation.resolutionrequired"resolved_ai" | "handed_off" | "abandoned" | "unknown" | null
data.conversation.shopperrequiredShopperViewShopper identity as shown on lists (masked for agent/viewer, 06 §5.3).
data.conversation.shopper.emailrequiredstring | null
data.conversation.shopper.is_maskedrequiredboolean
data.conversation.shopper.is_verifiedrequiredboolean
data.conversation.shopper.namerequiredstring | null
data.conversation.started_atrequiredstringformat date-time
data.conversation.statusrequired"open" | "closed"
data.conversation.store_idrequiredstringpattern `^sto_[0-9a-hjkmnp-tv-z]{26}$`
data.conversation.tagsrequiredstring[]
data.dashboard_urlrequiredstringformat uri · ≤ 2048 characters
data.handoffrequiredHandoffAdmin`HandoffAdmin = HandoffState & {…}` (03 §6).
data.handoff.accepted_atrequiredstring | nullformat date-time
data.handoff.agentrequiredAgentRef | null
data.handoff.agent.display_namerequiredstring
data.handoff.delivered_atrequiredstring | nullformat date-time
data.handoff.expected_wait_secondsrequiredinteger | null
data.handoff.idrequiredstringpattern `^hof_[0-9a-hjkmnp-tv-z]{26}$`
data.handoff.messagerequiredstringLocalized shopper-facing status text
data.handoff.reasonrequiredstring (enum)one of shopper_request, ai_escalation, action_request, policy, repeated_failure, error, outside_hours, quota_block, limit_reached
data.handoff.requested_atrequiredstringformat date-time
data.handoff.requested_byrequired"shopper" | "ai" | "agent" | "system"
data.handoff.shopperrequiredHandoffShopper`HandoffAdmin.shopper` (`platform_customer_id` only for `identity_level = customer`).
data.handoff.shopper.emailrequiredstring | null
data.handoff.shopper.email_verifiedrequiredboolean
data.handoff.shopper.identity_levelrequired"guest" | "order_lookup" | "order_otp" | "customer"
data.handoff.shopper.namerequiredstring | null
data.handoff.shopper.platform_customer_idrequiredstring | null
data.handoff.staterequired"requested" | "waiting_for_agent" | "live" | "delivered_offline" | "failed" | "resolved"
data.handoff.statusrequiredstring (enum)one of requested, waiting_for_agent, live, delivering, delivered, failed, resolved, cancelled
data.handoff.summaryrequiredstring | null
data.handoff.targetrequired"live_chat" | "email" | "helpdesk" | null
data.handoff.targetsrequiredHandoffTargetView[]
data.handoff.targets[].external_urlrequiredstring | nullformat uri · ≤ 2048 characters
data.handoff.targets[].integration_idrequiredstring | nullpattern `^int_[0-9a-hjkmnp-tv-z]{26}$`
data.handoff.targets[].last_errorrequiredstring | null
data.handoff.targets[].statusrequired"pending" | "delivered" | "failed" | "skipped"
data.handoff.targets[].typerequiredstring
data.transcriptrequiredApiMessage[]≤ 50 items
data.transcript[].authorrequiredMessageAuthorWho wrote a message.
data.transcript[].author.display_namerequiredstring | null
data.transcript[].author.typerequired"shopper" | "ai" | "human" | "system"
data.transcript[].blocksrequiredUiBlock[]
data.transcript[].created_atrequiredstringformat date-time
data.transcript[].idrequiredstringpattern `^msg_[0-9a-hjkmnp-tv-z]{26}$`
data.transcript[].is_ai_generatedrequiredboolean
data.transcript[].rolerequired"shopper" | "assistant" | "agent" | "system"
data.transcript[].seqrequiredinteger≥ 0
data.transcript[].textrequiredstring
idrequiredstringpattern `^evt_[0-9a-hjkmnp-tv-z]{26}$`
organization_idrequiredstringpattern `^org_[0-9a-hjkmnp-tv-z]{26}$`
store_idrequiredstring | nullnull for organization-level events (usage)pattern `^sto_[0-9a-hjkmnp-tv-z]{26}$`
typerequired"handoff.requested"
{
  "api_version": "v1",
  "created_at": "2026-09-28T10:15:30.123Z",
  "data": {
    "conversation": {
      "assigned_user_id": "usr_01j8000000e008000000000000",
      "channel": "widget",
      "closed_at": "2026-09-28T10:15:30.123Z",
      "csat_score": 1,
      "handoff_requested_at": "2026-09-28T10:15:30.123Z",
      "handoff_state": "none",
      "id": "conv_01j8000000e008000000000000",
      "identity_level": "guest",
      "is_test": true,
      "last_message_at": "2026-09-28T10:15:30.123Z",
      "locale": "da-DK",
      "message_count": 0,
      "mode": "ai",
      "platform_customer_id": "string",
      "resolution": "resolved_ai",
      "shopper": {
        "email": "string",
        "is_masked": true,
        "is_verified": true,
        "name": "string"
      },
      "started_at": "2026-09-28T10:15:30.123Z",
      "status": "open",
      "store_id": "sto_01j8000000e008000000000000",
      "tags": [
        "string"
      ]
    },
    "dashboard_url": "https://shop.example/products/linen-shirt",
    "handoff": {
      "agent": null,
      "expected_wait_seconds": 120,
      "id": "hof_01j8000000e008000000000001",
      "message": "We've let the team know. Someone will be with you shortly.",
      "state": "waiting_for_agent",
      "target": "live_chat"
    },
    "transcript": [
      {
        "author": {
          "display_name": "string",
          "type": "shopper"
        },
        "blocks": [
          {
            "as_of": "2026-09-28T10:15:30.123Z",
            "id": "b1",
            "items": [
              {
                "actions": {
                  "add_to_cart": true,
                  "check_store_stock": true,
                  "notify_back_in_stock": false
                },
                "as_of": "2026-09-28T10:15:30.123Z",
                "availability": {
                  "as_of": "2026-09-28T10:15:30.123Z",
                  "label": "Only 2 left",
                  "quantity": 2,
                  "status": "low_stock"
                },
                "badges": [
                  "New"
                ],
                "default_variant_id": "var_01j8000000e008000000000001",
                "image": {
                  "alt": "City bike",
                  "height": 600,
                  "url": "https://cdn.shopify.com/s/files/1/demo/bike-1.jpg",
                  "width": 800
                },
                "options": [
                  {
                    "name": "Size",
                    "values": [
                      {
                        "is_available": true,
                        "value": "M"
                      },
                      {
                        "is_available": false,
                        "value": "L"
                      }
                    ]
                  }
                ],
                "price": {
                  "amount": "4999",
                  "currency": "DKK",
                  "display": "4.999,00 kr."
                },
                "product_id": "prd_01j8000000e008000000000001",
                "rating": {
                  "average": 4.6,
                  "count": 128
                },
                "subtitle": "Nordic Bikes",
                "title": "Nordic City Bike 7-speed",
                "url": "https://nordicbikes.example/products/city-bike?utm_source=shopassist&utm_medium=chat",
                "variants": [
                  {
                    "availability": {
                      "as_of": "2026-09-28T10:15:30.123Z",
                      "label": "Only 2 left",
                      "quantity": 2,
                      "status": "low_stock"
                    },
                    "cart_ref": {
                      "variant_gid": "gid://shopify/ProductVariant/4711"
                    },
                    "options": {
                      "Size": "M"
                    },
                    "price": {
                      "amount": "4999",
                      "currency": "DKK",
                      "display": "4.999,00 kr."
                    },
                    "title": "M",
                    "variant_id": "var_01j8000000e008000000000001"
                  }
                ]
              }
            ],
            "layout": "carousel",
            "title": "City bikes in stock",
            "type": "products"
          }
        ],
        "created_at": "2026-09-28T10:15:30.123Z",
        "id": "msg_01j8000000e008000000000000",
        "is_ai_generated": true,
        "role": "shopper",
        "seq": 0,
        "text": "string"
      }
    ]
  },
  "id": "evt_01j8000000e008000000000000",
  "organization_id": "org_01j8000000e008000000000000",
  "store_id": "sto_01j8000000e008000000000000",
  "type": "handoff.requested"
}

Verify every delivery as described in verifying signatures. Because webhooks are notifications only, also keep a shopper-facing target (email or a helpdesk) in your list.

Last updated on

On this page