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
POSTrequests - 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:
| Header | Description |
|---|---|
Content-Type | Must be application/json |
X-Shipit-Timestamp | Unix timestamp in seconds |
X-Shipit-Nonce | Unique random string per request |
X-Shipit-Signature | Lowercase hex HMAC-SHA256 signature |
X-Shipit-Caller | Caller 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:
jsonrpcmust be2.0idmust be echoed back unchangedmethodselects the provider functionparamscontains 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:
200for valid JSON-RPC success or business errors400for malformed JSON401or403for failed authentication or signature validation429for rate limiting500for unexpected provider failures
Canonical error codes
Use the standard JSON-RPC protocol codes whenever the failure is about the JSON-RPC envelope itself.
| Code | HTTP status | When to use it |
|---|---|---|
-32700 | 400 | Malformed JSON that could not be parsed at all |
-32600 | 200 | Invalid JSON-RPC request object |
-32601 | 200 | Unknown RPC method |
-32602 | 200 | Invalid method params |
-32603 | 500 | Unexpected provider-side internal error |
Use the following application-defined codes for provider-specific failures that Shipit may need to inspect:
| Code | HTTP status | When to use it |
|---|---|---|
40100 | 401 | params.shop is missing before auth verification can start |
40101 | 401 | Unknown shop identifier or no shared secret for that shop |
40102 | 401 | Required HMAC headers are missing |
40103 | 401 | X-Shipit-Timestamp is not a Unix timestamp in seconds |
40104 | 401 | Timestamp is outside the allowed skew window |
40105 | 401 | Nonce replay was detected |
40106 | 401 | HMAC signature does not match the request |
40401 | 200 | Requested shop resource was not found |
42201 | 200 | Business 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:
| Method | Purpose |
|---|---|
shipit.return_and_exchange.order.search | Find orders from order number plus email or phone |
shipit.return_and_exchange.order.get | Fetch one full order |
shipit.return_and_exchange.order.returnable_fulfillments.get | Fetch the fulfilled items that can still be returned |
shipit.return_and_exchange.return.create | Create a return |
shipit.return_and_exchange.exchange.create | Create an exchange |
shipit.return_and_exchange.refund.create | Create a refund |
shipit.return_and_exchange.gift_card.create | Create a gift card or store credit |
shipit.return_and_exchange.order.metafield.set | Store order-level metadata |
shipit.return_and_exchange.product.get | Fetch one product together with its variants |
shipit.return_and_exchange.product_variant.get | Fetch one product variant |
shipit.return_and_exchange.product.search | Find 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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
order_number | string | yes | Customer-visible order number such as #2149 |
email_or_phone | string | yes | Customer 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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
order_id | string | yes | Provider 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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
order_id | string | yes | Provider 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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
order_id | string | yes | Provider order ID |
return_line_items | array | yes | At least one line item |
return_line_items[].quantity | integer | yes | Minimum 1 |
return_line_items[].line_item_id | string | conditional | Required when fulfillment_line_item_id is not sent |
return_line_items[].fulfillment_line_item_id | string | conditional | Required when line_item_id is not sent |
return_line_items[].return_reason | string | no | Provider-specific return reason |
return_line_items[].return_reason_note | string | no | Free-text note |
tracking_number | string | no | Reverse shipment tracking number |
tracking_url | string | no | Tracking URL when needed |
idempotency_key | string | yes | Retry-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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
order_id | string | yes | Provider order ID |
return_line_items | array | yes | Items being returned |
exchange_line_items | array | yes | Replacement items |
exchange_line_items[].variant_id | string | yes | Provider variant ID |
exchange_line_items[].quantity | integer | yes | Minimum 1 |
idempotency_key | string | yes | Retry-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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
order_id | string | yes | Provider order ID |
refund_line_items | array | yes | Refund line items |
note | string | no | Refund note |
notify_customer | boolean | no | Defaults to false |
transactions | array | no | Provider-specific transaction descriptors |
idempotency_key | string | yes | Retry-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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
customer_id | string | yes | Provider customer ID |
initial_value | string | yes | Decimal money string |
note | string | no | Gift card note |
idempotency_key | string | yes | Retry-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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
order_id | string | yes | Provider order ID |
namespace | string | yes | Namespace such as shipit |
key | string | yes | Key such as return_id |
value | mixed | yes | Structured or scalar value |
type | string | no | Defaults to json |
idempotency_key | string | yes | Retry-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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
product_id | string | yes | Provider 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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
variant_id | string | yes | Provider 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:
| Field | Type | Required | Notes |
|---|---|---|---|
shop | string | yes | Merchant or storefront identifier |
price | number | yes | Target price |
product_type | string | no | Optional product-type filter |
limit | integer | no | Defaults 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
- Send a
shipit.return_and_exchange.order.searchrequest to your provider endpoint using the shared secret configured for that merchant in Shipit. - Confirm the provider returns a valid JSON-RPC success response with a full order object inside
result.orders. - Send
shipit.return_and_exchange.order.returnable_fulfillments.getfor that same order and confirm the provider returns eligible fulfillment line items. - Send a write method such as
shipit.return_and_exchange.return.createtwice with the sameidempotency_keyand confirm it does not create duplicates. - 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_keyhandling 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
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.
