Shipit DocumentationShipit Documentation
Home
API
Webhooks
Shipit Delivery Checkout
Shipit Return and Exchange
Shopify Delivery Checkout
  • English
  • Suomi
  • Svenska
  • Eesti
  • Dansk
  • Norsk
Home
API
Webhooks
Shipit Delivery Checkout
Shipit Return and Exchange
Shopify Delivery Checkout
  • English
  • Suomi
  • Svenska
  • Eesti
  • Dansk
  • Norsk
  • Shipit Return and Exchange

    • Shipit Return and Exchange
  • Integration & APIs

    • Return Provider RPC Protocol

Return Provider RPC Protocol

TL;DR

This protocol is the provider integration contract for Shipit Return and Exchange. It is not the Shopify Delivery Checkout API.

Implement one HTTPS POST endpoint that accepts JSON-RPC 2.0 requests from Shipit Return and Exchange, uses the shared secret generated in the merchant's return-tool settings, and implements the 11 methods listed on this page.

What You Are Trying To Achieve

You want Shipit Return and Exchange to run return, refund, exchange, and store-credit workflows against your own backend instead of talking to Shopify directly.

To do that, you expose one provider RPC endpoint. Shipit calls that endpoint whenever it needs to:

  • find an order from the customer-facing order number and email or phone
  • load full order details
  • determine which fulfilled items are returnable
  • create returns, refunds, exchanges, gift cards, or order metadata updates
  • load product and variant data for exchange selection

Before You Begin

You need:

  • one HTTPS endpoint URL that accepts POST requests
  • the 64-character HMAC shared secret shown in the merchant's return-tool settings inside Shipit
  • stable IDs for shops, orders, customers, products, variants, line items, fulfillment line items, returns, refunds, and gift cards
  • idempotency handling for write operations
  • UTC timestamps and replay protection

Step-by-Step Walkthrough

1. Expose one provider endpoint

Shipit sends every RPC call to the exact URL you configure, for example:

https://returns-provider.example.com/shipit-return-exchange/rpc

Shipit does not append extra resource paths.

2. Configure the endpoint in the merchant's return-tool settings

Shipit does not keep one global RPC endpoint for the whole app.

Each merchant that enables the RPC mode in Return and Exchange settings provides:

  • their own HTTPS endpoint URL
  • their own generated shared secret

Shipit then signs that merchant's requests with HMAC-SHA256.

3. Verify HMAC requests

Each request is signed with HMAC-SHA256.

Required headers:

HeaderDescription
Content-TypeMust be application/json
X-Shipit-TimestampUnix timestamp in seconds
X-Shipit-NonceUnique random string per request
X-Shipit-SignatureLowercase hex HMAC-SHA256 signature
X-Shipit-CallerCaller identifier for tracing and allow-listing

Canonical string:

{timestamp}\n{nonce}\n{raw_json_body}

Signature:

hex(hmac_sha256(shared_secret, canonical_string))

Validation rules:

  • reject timestamps older than 300 seconds or too far in the future
  • reject reused nonces for at least 10 minutes
  • reject requests when the signature does not match exactly

4. Parse the JSON-RPC envelope

Every request uses JSON-RPC 2.0:

{
  "jsonrpc": "2.0",
  "id": "req_01jx8m6f8x4rz7e4fah5m8a0tb",
  "method": "shipit.return_and_exchange.order.search",
  "params": {
    "shop": "merchant.example",
    "order_number": "#2149",
    "email_or_phone": "[email protected]"
  }
}

Rules:

  • jsonrpc must be 2.0
  • id must be echoed back unchanged
  • method selects the provider function
  • params contains the business payload
  • public method segments and payload keys use snake_case

5. Return JSON-RPC success or error responses

Success response:

{
  "jsonrpc": "2.0",
  "id": "req_01jx8m6f8x4rz7e4fah5m8a0tb",
  "result": {
    "orders": []
  }
}

Error response:

{
  "jsonrpc": "2.0",
  "id": "req_01jx8m6f8x4rz7e4fah5m8a0tb",
  "error": {
    "code": 42201,
    "message": "Validation failed",
    "data": {
      "field": "order_number"
    }
  }
}

Use normal HTTP status codes too:

  • 200 for valid JSON-RPC success or business errors
  • 400 for malformed JSON
  • 401 or 403 for failed authentication or signature validation
  • 429 for rate limiting
  • 500 for unexpected provider failures

Canonical error codes

Use the standard JSON-RPC protocol codes whenever the failure is about the JSON-RPC envelope itself.

CodeHTTP statusWhen to use it
-32700400Malformed JSON that could not be parsed at all
-32600200Invalid JSON-RPC request object
-32601200Unknown RPC method
-32602200Invalid method params
-32603500Unexpected provider-side internal error

Use the following application-defined codes for provider-specific failures that Shipit may need to inspect:

CodeHTTP statusWhen to use it
40100401params.shop is missing before auth verification can start
40101401Unknown shop identifier or no shared secret for that shop
40102401Required HMAC headers are missing
40103401X-Shipit-Timestamp is not a Unix timestamp in seconds
40104401Timestamp is outside the allowed skew window
40105401Nonce replay was detected
40106401HMAC signature does not match the request
40401200Requested shop resource was not found
42201200Business validation failed for an otherwise valid RPC request

If you need more provider-specific validation or business error codes, keep them outside the reserved JSON-RPC ranges and document them for merchants.

Method Reference

Current v1 method surface

These are the methods Shipit Return and Exchange currently needs from a provider integration:

MethodPurpose
shipit.return_and_exchange.order.searchFind orders from order number plus email or phone
shipit.return_and_exchange.order.getFetch one full order
shipit.return_and_exchange.order.returnable_fulfillments.getFetch the fulfilled items that can still be returned
shipit.return_and_exchange.return.createCreate a return
shipit.return_and_exchange.exchange.createCreate an exchange
shipit.return_and_exchange.refund.createCreate a refund
shipit.return_and_exchange.gift_card.createCreate a gift card or store credit
shipit.return_and_exchange.order.metafield.setStore order-level metadata
shipit.return_and_exchange.product.getFetch one product together with its variants
shipit.return_and_exchange.product_variant.getFetch one product variant
shipit.return_and_exchange.product.searchFind products for exchange suggestions

Method inputs and outputs

shipit.return_and_exchange.order.search

Use this to find a customer order from the customer-visible order number plus email or phone.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
order_numberstringyesCustomer-visible order number such as #2149
email_or_phonestringyesCustomer email or phone used to verify the order

Output:

{
  "orders": [
    {
      "id": "ord_1001",
      "name": "#2149",
      "email": "[email protected]",
      "phone": "+358401234567",
      "created_at": "2026-06-09T12:00:00Z",
      "cancelled_at": null,
      "closed_at": null,
      "display_financial_status": "PAID",
      "display_fulfillment_status": "FULFILLED",
      "amount": "89.90",
      "currency_code": "EUR",
      "shipping_address": {
        "name": "Pat Example",
        "first_name": "Pat",
        "last_name": "Example",
        "address1": "Testikatu 1",
        "address2": null,
        "city": "Helsinki",
        "province_code": "18",
        "country": "Finland",
        "country_code": "FI",
        "zip": "00100",
        "phone": "+358401234567"
      },
      "billing_address": null,
      "discount_codes": ["SUMMER10"],
      "tags": ["returned-before"],
      "metafields": {
        "shipit.return_id": "ret_5001"
      },
      "line_items": [
        {
          "line_item_id": "li_1",
          "id": "li_1",
          "title": "Blue T-Shirt",
          "name": "Blue T-Shirt / M",
          "quantity": 1,
          "sku": "TSHIRT-BLUE-M",
          "variant_id": "var_1",
          "variant_title": "M",
          "product_id": "prod_1",
          "product_type": "Shirts",
          "product_tags": ["tops"],
          "image": {
            "url": "https://cdn.example.com/products/blue-shirt.jpg"
          },
          "unit_price": "89.90"
        }
      ]
    }
  ]
}

Important:

  • return full order objects here, not just a search summary
  • Shipit currently consumes the first orders[] match directly as an order

shipit.return_and_exchange.order.get

Use this to fetch one full order by provider order ID.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
order_idstringyesProvider order ID

Output:

  • one order object in the same shape shown above for order.search

shipit.return_and_exchange.order.returnable_fulfillments.get

Use this to tell Shipit which fulfilled items are still eligible for return.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
order_idstringyesProvider order ID

Output:

{
  "returnable_fulfillments": [
    {
      "id": "rf_1",
      "fulfillment_id": "ful_1",
      "line_items": [
        {
          "fulfillment_line_item_id": "fli_1",
          "line_item_id": "li_1",
          "name": "Blue T-Shirt / M",
          "sku": "TSHIRT-BLUE-M",
          "variant_id": "var_1",
          "product_id": "prod_1",
          "returnable_quantity": 1,
          "amount": "89.90",
          "currency_code": "EUR"
        }
      ]
    }
  ]
}

shipit.return_and_exchange.return.create

Use this to create a return record.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
order_idstringyesProvider order ID
return_line_itemsarrayyesAt least one line item
return_line_items[].quantityintegeryesMinimum 1
return_line_items[].line_item_idstringconditionalRequired when fulfillment_line_item_id is not sent
return_line_items[].fulfillment_line_item_idstringconditionalRequired when line_item_id is not sent
return_line_items[].return_reasonstringnoProvider-specific return reason
return_line_items[].return_reason_notestringnoFree-text note
tracking_numberstringnoReverse shipment tracking number
tracking_urlstringnoTracking URL when needed
idempotency_keystringyesRetry-safe write key

Output:

{
  "return": {
    "id": "ret_5001",
    "status": "REQUESTED",
    "order_id": "ord_1001",
    "return_line_items": [
      {
        "line_item_id": "li_1",
        "quantity": 1,
        "return_reason": "too_small",
        "return_reason_note": "Customer needs one size bigger"
      }
    ]
  }
}

shipit.return_and_exchange.exchange.create

Use this when the customer wants a replacement item instead of only a refund.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
order_idstringyesProvider order ID
return_line_itemsarrayyesItems being returned
exchange_line_itemsarrayyesReplacement items
exchange_line_items[].variant_idstringyesProvider variant ID
exchange_line_items[].quantityintegeryesMinimum 1
idempotency_keystringyesRetry-safe write key

Output:

{
  "exchange": {
    "id": "exc_6001",
    "order_id": "ord_1001"
  }
}

shipit.return_and_exchange.refund.create

Use this to create a refund after the return is approved or processed.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
order_idstringyesProvider order ID
refund_line_itemsarrayyesRefund line items
notestringnoRefund note
notify_customerbooleannoDefaults to false
transactionsarraynoProvider-specific transaction descriptors
idempotency_keystringyesRetry-safe write key

Output:

{
  "refund": {
    "id": "ref_7001",
    "created_at": "2026-06-09T13:00:00Z",
    "note": "Customer returned one item",
    "amount": "89.90",
    "currency_code": "EUR"
  }
}

shipit.return_and_exchange.gift_card.create

Use this when the merchant issues store credit as a gift card.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
customer_idstringyesProvider customer ID
initial_valuestringyesDecimal money string
notestringnoGift card note
idempotency_keystringyesRetry-safe write key

Output:

{
  "gift_card": {
    "id": "gc_8001",
    "customer_id": "cust_1001",
    "initial_value": "89.90",
    "expires_on": null,
    "note": "Store credit for return"
  },
  "gift_card_code": "SHIPIT-ABCD-1234"
}

shipit.return_and_exchange.order.metafield.set

Use this to persist structured order-level metadata for Shipit workflows.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
order_idstringyesProvider order ID
namespacestringyesNamespace such as shipit
keystringyesKey such as return_id
valuemixedyesStructured or scalar value
typestringnoDefaults to json
idempotency_keystringyesRetry-safe write key

Output:

{
  "metafield": {
    "namespace": "shipit",
    "key": "return_id",
    "value": "ret_5001",
    "type": "json"
  }
}

shipit.return_and_exchange.product.get

Use this to load one product together with all selectable variants for an exchange flow.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
product_idstringyesProvider product ID

Output:

{
  "product": {
    "id": "prod_1",
    "title": "Blue T-Shirt",
    "product_type": "Shirts",
    "tags": ["tops"],
    "variants": [
      {
        "id": "var_1",
        "title": "M",
        "sku": "TSHIRT-BLUE-M",
        "price": "89.90",
        "available_for_sale": true,
        "inventory_policy": "DENY",
        "image": {
          "url": "https://cdn.example.com/products/blue-shirt.jpg"
        },
        "selected_options": [
          {
            "name": "Size",
            "value": "M"
          }
        ]
      }
    ]
  }
}

shipit.return_and_exchange.product_variant.get

Use this to load one variant directly when you already know the provider variant ID.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
variant_idstringyesProvider variant ID

Output:

  • one variant object in the same shape shown inside product.variants[]

shipit.return_and_exchange.product.search

Use this to find exchange alternatives by price and optionally by product type.

Input:

FieldTypeRequiredNotes
shopstringyesMerchant or storefront identifier
pricenumberyesTarget price
product_typestringnoOptional product-type filter
limitintegernoDefaults to 10

Output:

{
  "products": [
    {
      "id": "prod_1",
      "title": "Blue T-Shirt",
      "product_type": "Shirts",
      "tags": ["tops"],
      "variants": [
        {
          "id": "var_1",
          "title": "M",
          "sku": "TSHIRT-BLUE-M",
          "price": "89.90",
          "available_for_sale": true,
          "inventory_policy": "DENY",
          "image": {
            "url": "https://cdn.example.com/products/blue-shirt.jpg"
          },
          "selected_options": [
            {
              "name": "Size",
              "value": "M"
            }
          ]
        }
      ]
    }
  ]
}

Example Request Or Configuration

Example request:

{
  "jsonrpc": "2.0",
  "id": "req_01jx8n0h7m1rwmv9j7f3es1k8x",
  "method": "shipit.return_and_exchange.return.create",
  "params": {
    "shop": "merchant.example",
    "order_id": "ord_1001",
    "return_line_items": [
      {
        "fulfillment_line_item_id": "fli_1",
        "quantity": 1,
        "return_reason": "too_small",
        "return_reason_note": "Customer needs one size bigger"
      }
    ],
    "tracking_number": "JJFI1234567890",
    "tracking_url": "https://carrier.example/track/JJFI1234567890",
    "idempotency_key": "01jx8n1jzvbx08m0j8p0m15k0p"
  }
}

Example Response Or Expected Outcome

Example response:

{
  "jsonrpc": "2.0",
  "id": "req_01jx8n0h7m1rwmv9j7f3es1k8x",
  "result": {
    "return": {
      "id": "ret_5001",
      "status": "REQUESTED",
      "order_id": "ord_1001",
      "return_line_items": [
        {
          "line_item_id": "li_1",
          "quantity": 1,
          "return_reason": "too_small",
          "return_reason_note": "Customer needs one size bigger"
        }
      ]
    }
  }
}

How To Verify It Worked

  1. Send a shipit.return_and_exchange.order.search request to your provider endpoint using the shared secret configured for that merchant in Shipit.
  2. Confirm the provider returns a valid JSON-RPC success response with a full order object inside result.orders.
  3. Send shipit.return_and_exchange.order.returnable_fulfillments.get for that same order and confirm the provider returns eligible fulfillment line items.
  4. Send a write method such as shipit.return_and_exchange.return.create twice with the same idempotency_key and confirm it does not create duplicates.
  5. Run one full Shipit Return and Exchange flow against a staging merchant and confirm Shipit can search the order, load variants, and create the chosen return outcome without calling Shopify directly.

Common Mistakes Or Troubleshooting

  • Returning only search summaries from order.search. Shipit currently needs a full order object there.
  • Using a different shared secret than the one shown in the merchant's return-tool settings.
  • Signing a parsed JSON object instead of the raw JSON body.
  • Returning provider-specific field names instead of the field names documented on this page.
  • Omitting idempotency_key handling for writes.
  • Returning unstable IDs that change between calls.
  • Returning HTML error pages instead of JSON-RPC errors.

What To Do Next

  • Implement the 11 current methods first.
  • Reuse the same order and variant object shapes across all methods.
  • Run end-to-end tests from Shipit Return and Exchange against a staging provider before go-live.

Related Docs

  • Shipit Return and Exchange

Glossary Terms

  • Provider — The commerce system Shipit talks to over RPC.
  • Shop — The merchant or storefront identifier sent with each request.
  • Idempotency key — A client-supplied key that makes write retries safe.
  • Returnable fulfillment — A fulfilled line that is still eligible for return.
Last Updated: 6/13/26, 7:25 AM
Contributors: Brian Faust