Implementação do ciclo de vida do pedido

Este guia fornece a referência técnica da API e os esquemas de payload para enviar atualizações completas do status do pedido, eventos de fulfillment e ajustes ao Google usando webhooks para a versão 2026-04-08 do Universal Commerce Protocol (UCP).

Antes de criar seus endpoints, consulte a Visão geral do ciclo de vida do pedido para conferir os conceitos gerais , eventos obrigatórios e detalhes do endpoint do webhook.

Autenticação e assinatura de solicitação

As principais mudanças na versão 2026-04-08 incluem a introdução de novos cabeçalhos de webhook obrigatórios e procedimentos específicos de assinatura de solicitação.

Cabeçalhos de webhook obrigatórios

Os seguintes cabeçalhos HTTP são obrigatórios para todas as solicitações de webhook:

  • Webhook-Id: um identificador exclusivo para esse evento de webhook específico. Esse ID precisa corresponder ao id do evento principal que está sendo enviado (por exemplo, o ID do evento de fulfillment ou o ID do evento de ajuste).
  • Webhook-Timestamp: o carimbo de data/hora que indica quando o evento ocorreu.

Esses cabeçalhos substituem os campos id e created_time esperados anteriormente no payload do pedido.

Assinatura de solicitação

  1. Calcule o resumo SHA-256 do corpo da solicitação bruta e defina o cabeçalho Content-Digest.
  2. Selecione uma chave de assinatura em signing_keys no seu perfil do UCP.
  3. Crie a base de assinatura de acordo com RFC 9421.
    • Consulte a especificação de componentes assinados.
  4. Defina os cabeçalhos UCP-Agent, Signature-Input e Signature.
    • UCP-Agent é um link para seu perfil do UCP no formato profile="https://merchant.example.com/.well-known/ucp".
    • Signature-Input é um campo estruturado de dicionário que descreve os componentes contidos na assinatura, bem como o keyid usado para assinar, que precisa corresponder ao kid da chave de assinatura selecionada em signing_keys no seu perfil do UCP.
    • O cabeçalho Signature contém sua base de assinatura, que é assinada usando sua chave privada e codificada em base64.

Consulte as instruções de assinatura em ucp.dev para mais informações.

Evento de pedido criado

  • Acionador: imediatamente após a confirmação do pedido (status: processing).

Principais mudanças nesta versão :

  • O campo currency agora é obrigatório no nível superior do objeto Order.
  • O campo type em cada objeto na matriz totals agora é uma string aberta (por exemplo, "subtotal", "tax", "fee", "total").
  • O cabeçalho obrigatório Webhook-Id contém um identificador exclusivo para o evento de webhook. O campo id no payload que contém o ID de confirmação do pedido ainda é obrigatório.
  • O cabeçalho obrigatório Webhook-Timestamp fornece o horário de criação, substituindo o campo created_time anterior no payload.

Exemplo: este exemplo mostra um pedido criado depois que um comprador conclui o checkout.

Cabeçalhos obrigatórios :

  • Webhook-Id: order_01
  • Webhook-Timestamp: 2026-03-23T19:00:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_01",
  "checkout_id": "checkout_01",
  "currency": "USD",
   // Always include all line items, even for single-item checkouts. This ensures any add-ons, gifts, or separate charges are accounted for.
  "line_items": [
    {
      "id": "line_1",
      "item":
        {
          "id": "product_123",
          "title": "Running Shoes",
          "price": 10000
        },
      "quantity": { "total": 1, "fulfilled": 0 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 10000},
        {"type": "total", "display_text": null, "amount": 10000}
      ],
      // The status of a line item must match total and fulfilled quantities (e.g., total 1, fulfilled 0 -> status: processing).
      "status": "processing"
    }
  ],
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 10000}, // Tax-inclusive markets: Set display_text to "Subtotal (including taxes)". Amount must include tax.
    {"type": "fee", "display_text": "Service Fee", "amount": 100},
    {"type": "tax", "display_text": "Tax", "amount": 800}, // Tax-inclusive markets: Omit this entry.
    {"type": "total", "display_text": "Total", "amount": 10900}
  ],
  "fulfillment": {
    "expectations": [
      {
        "id": "exp_1",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "method_type": "shipping",
        "destination": {
          "first_name": "Alice",
          "last_name": "Example",
          "street_address": "123 Main St",
          "address_locality": "Austin",
          "address_region": "TX",
          "address_country": "US",
          "postal_code": "78701"
        },
        "description": "Arrives in 2-3 business days",  // Maximum length: 200 characters.
        "fulfillable_on": "now"
      }
    ]
  },
  "permalink_url": "https://merchant.example.com/orders/789"
}

Eventos de fulfillment

Esses eventos são enviados como parte da matriz fulfillment.events.

Pedido enviado

Quando os itens do pedido foram enviados. Os campos tracking_number e tracking_url são obrigatórios para eventos enviados, porque são necessários para agrupar os itens na página "Meus pedidos" com precisão.

Pedido entregue

Quando os itens do pedido foram entregues.

Exemplo (shipped e delivered) : este exemplo mostra uma atualização de pedido depois que o item foi enviado e entregue.

Cabeçalhos obrigatórios :

  • Webhook-Id: fulfill_evt_2
  • Webhook-Timestamp: 2026-02-10T14:00:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_01",
  "checkout_id": "checkout_01",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_1",
      "item":
        {
          "id": "product_123",
          "title": "Running Shoes",
          "price": 10000
        },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "amount": 10000},
        {"type": "total", "amount": 10000}
      ],
      "status": "fulfilled"
    }
  ],
  "totals": [
    {"type": "subtotal", "amount": 10000},
    {"type": "total", "amount": 10000}
  ],
  // Updated fulfillment details
  "fulfillment": {
    "events": [
      {
        "id": "fulfill_evt_1",
        "occurred_at": "2026-02-08T10:30:00Z",
        "type": "shipped",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "tracking_number": "123456789",
        "tracking_url": "https://fedex.com/track/123456789",
        "carrier": "FedEx",
        "description": "Shipping departed from warehouse"
      },
      {
        "id": "fulfill_evt_2",
        "occurred_at": "2026-02-10T14:00:00Z",
        "type": "delivered",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "tracking_number": "123456789",
        "tracking_url": "https://fedex.com/track/123456789",
        "carrier": "FedEx",
        "description": "Package delivered"
      }
    ],
    "expectations": [{ "...": "..." }]
  },
  "permalink_url": "https://merchant.example.com/orders/123"
}

Exemplos de pedidos com vários itens

Os exemplos a seguir demonstram como estruturar atualizações para pedidos com vários itens e envios divididos. Para conferir as regras sobre como os status dos pacotes são derivados, consulte Como o status do pacote é determinado.

Pedido com vários itens, entrega do mesmo pacote

Este exemplo mostra uma atualização de pedido para um único pacote que contém vários itens. Todos os itens de linha são agrupados em um único evento shipped que compartilha o mesmo número de rastreamento.

Cabeçalhos obrigatórios :

  • Webhook-Id: fulfill_evt_1
  • Webhook-Timestamp: 2026-02-08T10:30:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_multi_01",
  "checkout_id": "checkout_multi_01",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_1",
      "item": { "id": "product_1", "title": "Item 1", "price": 5000 },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 5000},
        {"type": "total", "display_text": null, "amount": 5000}
      ],
      "status": "fulfilled"
    },
    {
      "id": "line_2",
      "item": { "id": "product_2", "title": "Item 2", "price": 5000 },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 5000},
        {"type": "total", "display_text": null, "amount": 5000}
      ],
      "status": "fulfilled"
    }
  ],
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 10000},
    {"type": "fee", "display_text": "Shipping", "amount": 500},
    {"type": "total", "display_text": "Total", "amount": 10500}
  ],
  "fulfillment": {
    "expectations": [
      {
        "id": "exp_1",
        "line_items": [
          { "id": "line_1", "quantity": 1 },
          { "id": "line_2", "quantity": 1 }
        ],
        "method_type": "shipping",
        "destination": {
          "first_name": "Alice",
          "last_name": "Example",
          "street_address": "123 Main St",
          "address_locality": "Austin",
          "address_region": "TX",
          "address_country": "US",
          "postal_code": "78701"
        },
        "description": "Standard Shipping",
        "fulfillable_on": "now"
      }
    ],
    "events": [
      {
        "id": "fulfill_evt_1",
        "occurred_at": "2026-02-08T10:30:00Z",
        "type": "shipped",
        "line_items": [
          { "id": "line_1", "quantity": 1 },
          { "id": "line_2", "quantity": 1 }
        ],
        "tracking_number": "123456789",
        "tracking_url": "https://fedex.com/track/123456789",
        "carrier": "FedEx",
        "description": "Both items shipped together"
      }
    ]
  },
  "permalink_url": "https://merchant.example.com/orders/456"
}

Pedido com vários itens, envio dividido

Este exemplo mostra uma atualização de pedido para envios divididos. Há vários eventos shipped, cada um referenciando os line_items específicos dentro do pacote respectivo, com números de rastreamento distintos. Como cada pacote representa um compromisso de fulfillment distinto (por exemplo, velocidades e custos diferentes de um checkout de vários grupos), as expectations também são divididas.

Cabeçalhos obrigatórios :

  • Webhook-Id: fulfill_evt_2
  • Webhook-Timestamp: 2026-02-09T14:00:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_multi_02",
  "checkout_id": "checkout_multi_02",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_1",
      "item": { "id": "product_1", "title": "Item 1", "price": 5000 },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 5000},
        {"type": "total", "display_text": null, "amount": 5000}
      ],
      "status": "fulfilled"
    },
    {
      "id": "line_2",
      "item": { "id": "product_2", "title": "Item 2", "price": 5000 },
      "quantity": { "total": 1, "fulfilled": 1 },
      "totals": [
        {"type": "subtotal", "display_text": null, "amount": 5000},
        {"type": "total", "display_text": null, "amount": 5000}
      ],
      "status": "fulfilled"
    }
  ],
  "totals": [
    {"type": "subtotal", "display_text": "Subtotal", "amount": 10000},
    {"type": "fee", "display_text": "Shipping", "amount": 1500},
    {"type": "total", "display_text": "Total", "amount": 11500}
  ],
  "fulfillment": {
    "expectations": [
      {
        "id": "exp_1",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "method_type": "shipping",
        "destination": {
          "first_name": "Alice",
          "last_name": "Example",
          "street_address": "123 Main St",
          "address_locality": "Austin",
          "address_region": "TX",
          "address_country": "US",
          "postal_code": "78701"
        },
        "description": "Standard Shipping",
        "fulfillable_on": "now"
      },
      {
        "id": "exp_2",
        "line_items": [{ "id": "line_2", "quantity": 1 }],
        "method_type": "shipping",
        "destination": {
          "first_name": "Alice",
          "last_name": "Example",
          "street_address": "123 Main St",
          "address_locality": "Austin",
          "address_region": "TX",
          "address_country": "US",
          "postal_code": "78701"
        },
        "description": "Express Shipping",
        "fulfillable_on": "now"
      }
    ],
    "events": [
      {
        "id": "fulfill_evt_1",
        "occurred_at": "2026-02-08T10:30:00Z",
        "type": "shipped",
        "line_items": [{ "id": "line_1", "quantity": 1 }],
        "tracking_number": "PKG1_TRACKING",
        "tracking_url": "https://fedex.com/track/PKG1_TRACKING",
        "carrier": "FedEx",
        "description": "First item shipped in package 1"
      },
      {
        "id": "fulfill_evt_2",
        "occurred_at": "2026-02-09T14:00:00Z",
        "type": "shipped",
        "line_items": [{ "id": "line_2", "quantity": 1 }],
        "tracking_number": "PKG2_TRACKING",
        "tracking_url": "https://fedex.com/track/PKG2_TRACKING",
        "carrier": "FedEx",
        "description": "Second item shipped in package 2"
      }
    ]
  },
  "permalink_url": "https://merchant.example.com/orders/457"
}

Exemplos de eventos de ajuste

Os exemplos a seguir demonstram como estruturar atualizações para reembolsos, devoluções e cancelamentos. Para conferir uma lista de eventos compatíveis e as definições deles, consulte Eventos de ajuste na Visão geral do ciclo de vida do pedido.

Cancelamento e reembolso do pedido

Este exemplo mostra um pedido em que o item foi cancelado e reembolsado logo após a realização do pedido.

Principais mudanças nesta versão neste exemplo :

  • Os itens de linha afetados por um cancellation agora usam "status": "removed" na matriz line_items principal.
  • Quando line_items.status é removed:
    • O line_items.quantity.total é definido como 0.
    • A quantidade original é armazenada no novo campo line_items.quantity.original.

Cabeçalhos obrigatórios :

  • Webhook-Id: adj_refund_1
  • Webhook-Timestamp: 2026-02-09T11:05:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_02",
  "checkout_id": "checkout_02",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_2",
      "item": {
        "id": "product_456",
        "title": "Smart Watch",
        "price": 29900
      },
      "quantity": {
        "total": 0,  // Item removed from order total.
        "original": 1,  // Original quantity before removal.
        "fulfilled": 0 //  Item was not fulfilled before cancellation.
      },
      "totals": [
        {"type": "subtotal", "amount": 29900},
        {"type": "tax", "amount": 2400},
        {"type": "total", "amount": 32300}
      ],
      "status": "removed" // Item is cancelled, returned, or refunded.
    }
  ],
  "totals": [
    {"type": "subtotal", "amount": 29900},
    {"type": "tax", "amount": 2400},
    {"type": "total", "amount": 32300}
  ],
    // Fulfillment expectations should still be present even if cancelled early.
  "fulfillment": {
    "expectations": [
      {
        "id": "exp_1",
        "line_items": [{ "id": "line_2", "quantity": 1 }],
        "method_type": "shipping",
        "destination": {
          "first_name": "Bob",
          "last_name": "Consumer",
          "street_address": "456 Oak Ave",
          "address_locality": "Anytown",
          "address_region": "CA",
          "address_country": "US",
          "postal_code": "90210"
        },
        "description": "Standard Shipping",
        "fulfillable_on": "now"
      }
    ]
    // "events": [] // No fulfillment events occurred before cancellation.
    },
  "adjustments": [
    {
      "id": "adj_cancel_1",
      "type": "cancellation",
      "description": "Customer changed mind",
      "line_items": [{ "id": "line_2", "quantity": -1 }],
      "occurred_at": "2026-02-09T11:00:00Z",
      "status": "completed"
    },
    {
      "id": "adj_refund_1",
      "type": "refund",
      "description": "Refund for cancelled item",
      "line_items": [{ "id": "line_2", "quantity": -1 }],
      "totals": [
        {"type": "subtotal", "display_text": "Subtotal", "amount": -29900}, // Negative amounts indicate money returned to the buyer. Tax-inclusive markets: Set display_text to "Subtotal (including taxes)". Amount must include tax.
        {"type": "tax", "amount": -2400}, // Tax-inclusive markets: Omit this entry.
        {"type": "total", "display_text": "Total", "amount": -32300}
      ],
      "occurred_at": "2026-02-09T11:05:00Z",
      "status": "completed"
    }
  ],
  "permalink_url": "https://merchant.example.com/orders/12345"
}

Devolução e reembolso do pedido

Este exemplo mostra um pedido em que o item foi enviado, entregue e, em seguida, devolvido e reembolsado.

Principais mudanças nesta versão neste exemplo :

  • Os itens de linha afetados por um return agora usam "status": "removed" na matriz principal line_items.
  • Quando line_items.status é removed:
    • O line_items.quantity.total é definido como 0.
    • A quantidade original é armazenada no novo campo line_items.quantity.original.
  • Em adjustments do tipo return, o campo line_items.quantity dentro do ajuste usa um valor negativo (por exemplo, -1) para indicar os itens que estão sendo devolvidos.

Cabeçalhos obrigatórios :

  • Webhook-Id: adj_refund_2
  • Webhook-Timestamp: 2026-02-10T10:00:00Z
{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [{"version": "2026-04-08"}]
    }
  },
  "id": "order_03",
  "checkout_id": "checkout_03",
  "currency": "USD",
  "line_items": [
    {
      "id": "line_3",
      "item": {
        "id": "product_789",
        "title": "Wireless Earbuds",
        "price": 14900
      },
      "quantity": {
        "total": 0,  // Item removed from order total.
        "original": 1,  // Original quantity before removal.
        "fulfilled": 1 // Was fulfilled before return.
      },
      "totals": [
        {"type": "subtotal", "amount": 14900},
        {"type": "tax", "amount": 1200},
        {"type": "total", "amount": 16100}
      ],
      "status": "removed" // Item is cancelled, returned, or refunded.
    }
  ],
  "totals": [
    {"type": "subtotal", "amount": 14900},
    {"type": "tax", "amount": 1200},
    {"type": "total", "amount": 16100}
  ],
  "fulfillment": {
    "events": [
      {
        "id": "fulfill_evt_1",
        "occurred_at": "2026-02-05T09:00:00Z",
        "type": "shipped",
        "line_items": [{ "id": "line_3", "quantity": 1 }],
        "tracking_number": "987654321",
        "tracking_url": "https://fedex.com/track/987654321",
        "carrier": "FedEx",
        "description": "Item shipped"
      },
      {
        "id": "fulfill_evt_2",
        "occurred_at": "2026-02-07T16:00:00Z",
        "type": "delivered",
        "line_items": [{ "id": "line_3", "quantity": 1 }],
        "tracking_number": "987654321",
        "tracking_url": "https://fedex.com/track/987654321",
        "carrier": "FedEx",
        "description": "Item delivered"
      },
      {
        "id": "fulfill_evt_3",
        "occurred_at": "2026-02-09T09:00:00Z",
        "type": "returned",
        "line_items": [{ "id": "line_3", "quantity": 1 }],
        "tracking_number": "987654321",
        "tracking_url": "https://fedex.com/track/987654321",
        "carrier": "FedEx",
        "description": "Item returned"
      }
    ],
    "expectations": [{ "...": "..." }]
  },
  "adjustments": [
    {
      "id": "adj_return_1",
      "type": "return", // a matching fulfillment event is also added to represent return shipping.
      "description": "Item not compatible",
      "line_items": [{ "id": "line_3", "quantity": -1 }],  // Uses a negative value (such as -1) to indicate a return.
      "occurred_at": "2026-02-09T09:00:00Z",
      "status": "completed"
    },
    {
      "id": "adj_refund_2",
      "type": "refund",
      "description": "Refund for returned item",
      "line_items": [{ "id": "line_3", "quantity": -1 }],
      "totals": [
        {"type": "subtotal", "amount": -14900}, // Negative amounts indicate money returned to the buyer.
        {"type": "tax", "amount": -1200},
        {"type": "total", "amount": -16100}
      ],
      "occurred_at": "2026-02-10T10:00:00Z",
      "status": "completed"
    }
  ],
  "permalink_url": "https://merchant.example.com/orders/67890"
}