주문 수명 주기 구현

이 가이드에서는 범용 커머스 프로토콜(UCP) 버전 2026-04-08의 웹훅을 사용하여 전체 주문 상태 업데이트, 주문 처리 이벤트, 조정을 Google에 푸시하기 위한 기술 API 참조 및 페이로드 스키마를 제공합니다.

엔드포인트를 빌드하기 전에 주문 수명 주기 개요에서 대략적인 개념, 필수 이벤트, 웹훅 엔드포인트 세부정보를 검토했는지 확인하세요.

인증 및 서명 요청

2026-04-08 버전의 주요 변경사항에는 새로운 필수 웹훅 헤더와 특정 서명 요청 절차의 도입이 포함됩니다.

필수 웹훅 헤더

모든 웹훅 요청에는 다음 HTTP 헤더가 필수입니다.

  • Webhook-Id: 이 특정 웹훅 이벤트의 고유 식별자입니다. 이 ID는 전송되는 기본 이벤트의 id (예: 주문 처리 이벤트 ID 또는 조정 이벤트 ID)와 일치해야 합니다.
  • Webhook-Timestamp: 이벤트가 발생한 시간을 나타내는 타임스탬프입니다.

이러한 헤더는 이전에 주문 페이로드에서 예상되었던 idcreated_time 필드를 대체합니다.

서명 요청

  1. 원시 요청 본문의 SHA-256 다이제스트를 계산하고 Content-Digest 헤더를 설정합니다.
  2. UCP 프로필의 signing_keys에서 서명 키를 선택합니다.
  3. RFC 9421에 따라 서명 기준을 빌드합니다.
    • 서명된 구성요소의 사양을 참고하세요.
  4. UCP-Agent, Signature-Input, Signature 헤더를 설정합니다.
    • UCP-Agentprofile="https://merchant.example.com/.well-known/ucp" 형식의 UCP 프로필 링크입니다.
    • Signature-Input 은 서명 내에 포함된 구성요소와 서명에 사용된 keyid를 설명하는 사전 구조 필드입니다. 이 keyid는 UCP 프로필의 signing_keys에서 선택한 서명 키의 kid와 일치해야 합니다.
    • Signature 헤더에는 비공개 키를 사용하여 서명한 후 base64로 인코딩된 서명 기준이 포함됩니다.

자세한 내용은 ucp.dev의 서명 안내 를 참고하세요.

주문 생성됨 이벤트

  • 트리거: 주문이 확인된 직후 (status: processing)

이 버전의 주요 변경사항:

  • 이제 Order 객체의 최상위 수준에서 currency 필드가 필요합니다.
  • 이제 totals 배열에 있는 각 객체의 type 필드는 개방형 문자열입니다 (예: '소계', '세금', '수수료', '총계').
  • 필수 Webhook-Id 헤더에는 웹훅 이벤트의 고유 식별자가 포함됩니다. 주문 확인 ID가 포함된 페이로드의 id 필드는 여전히 필요합니다.
  • 필수 Webhook-Timestamp 헤더는 생성 시간을 제공하며, 페이로드의 이전 created_time 필드를 대체합니다.

예: 이 예에서는 구매자가 결제를 완료한 후 생성된 주문을 보여줍니다. 결제.

필수 헤더:

  • 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"
}

주문 처리 이벤트

이러한 이벤트는 fulfillment.events 배열의 일부로 전송됩니다.

주문 발송

주문의 상품이 발송된 경우입니다. tracking_numbertracking_url 필드는 발송된 이벤트에 필요합니다. 이러한 필드는 '내 주문' 페이지에서 상품을 정확하게 그룹화하는 데 필요하기 때문입니다.

배송 완료

주문의 상품이 배송된 경우입니다.

예 (shippeddelivered): 이 예에서는 상품이 발송된 후 배송된 주문 업데이트를 보여줍니다.

필수 헤더:

  • 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"
}

여러 상품 주문 예

다음 예에서는 여러 상품 주문 및 분할 배송의 업데이트를 구성하는 방법을 보여줍니다. 패키지 상태가 파생되는 방법에 관한 규칙은 패키지 상태가 결정되는 방법을 참고하세요.

여러 상품 주문, 동일한 패키지 배송

이 예에서는 여러 상품이 포함된 단일 패키지의 주문 업데이트를 보여줍니다. 모든 광고 항목은 동일한 운송장 번호를 공유하는 단일 shipped 이벤트 내에 그룹화됩니다.

필수 헤더:

  • 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"
}

여러 상품 주문, 분할 배송

이 예에서는 분할 배송의 주문 업데이트를 보여줍니다. 각각 고유한 운송장 번호가 있는 여러 shipped 이벤트가 있으며, 각 이벤트는 해당 패키지 내의 특정 line_items를 참조합니다. 각 패키지는 고유한 주문 처리 약정 (예: 여러 그룹 결제에서 서로 다른 속도와 비용)을 나타내므로 expectations도 분할됩니다.

필수 헤더:

  • 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"
}

조정 이벤트 예

다음 예에서는 환불, 반품, 취소의 업데이트를 구성하는 방법을 보여줍니다. 지원되는 이벤트 및 정의 목록은 주문 수명 주기 개요의 조정 이벤트를 참고하세요.

주문 취소 및 환불

이 예에서는 주문이 접수된 직후 상품이 취소되고 환불된 주문을 보여줍니다.

이 예에서 이 버전의 주요 변경사항:

  • 이제 cancellation의 영향을 받는 광고 항목은 기본 line_items 배열에서 "status": "removed"를 사용합니다.
  • line_items.statusremoved인 경우:
    • line_items.quantity.total0으로 설정됩니다.
    • 원래 수량은 새 line_items.quantity.original 필드에 저장됩니다.

필수 헤더:

  • 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"
}

주문 반품 및 환불

이 예에서는 상품이 발송되고 배송된 후 반품되고 환불된 주문을 보여줍니다.

이 예에서 이 버전의 주요 변경사항:

  • 이제 return의 영향을 받는 광고 항목은 기본 line_items 배열에서 "status": "removed"를 사용합니다.
  • line_items.statusremoved인 경우:
    • line_items.quantity.total0으로 설정됩니다.
    • 원래 수량은 새 line_items.quantity.original 필드에 저장됩니다.
  • return 유형의 adjustments에서 조정 내의 line_items.quantity 필드는 음수 값 (예: -1)을 사용하여 반품되는 상품을 나타냅니다.

필수 헤더:

  • 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"
}