Webhook Sandbox

The webhook sandbox lets you trigger a webhook on demand, without placing an order and waiting for it to ship. WHCC builds the notification, signs it, and delivers it to your registered callback URI through the normal delivery path. Your handler cannot tell the difference except by the fields described on this page.

Sandbox environment only

These routes exist only on the WHCC sandbox environment. On https://apps.whcc.com they return 404 Not Found, and they always will. Do not build a production code path that calls them.

Use the same base URL and the same credentials you already use for sandbox work: https://sandbox.apps.whcc.com. This is the sandbox environment described in the Order Submit API overview.

Before you trigger anything

The sandbox delivers through the real delivery path, so it needs everything the real path needs. Three conditions must hold before a trigger succeeds:

  1. You have registered a callback URI.
  2. You have verified that callback URI.
  3. Your credentials have a consumer secret, which WHCC uses to sign the request.

The trigger checks all three before it writes anything, and answers each missing state with its own error:

Response Meaning
409 no_callback_registered No callback registered. Call /api/callback/create, then /api/callback/verify.
409 callback_not_verified The callback is registered but not verified. Call /api/callback/verify.
409 no_consumer_secret Your consumer has no secret, so the webhook would be delivered unsigned. Contact WHCC.

Every response on this page is JSON. Send an Authorization: Bearer header with a sandbox access token on every request. An expired or unknown token returns 403.

GET /api/Webhooks/Sandbox/Events

Discovery lists the events you can trigger, one sample payload each, and the reason for anything unavailable. Ask this endpoint rather than hard-coding the list.

Example Request

1
2
curl https://sandbox.apps.whcc.com/api/Webhooks/Sandbox/Events \
	-H "Authorization: Bearer 726670514499"
Copy to Clipboard

Example Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
[
  {
    "slug": "Shipped",
    "available": true,
    "sample": {
      "ShippingInfo": [
        {
          "Carrier": "FedEx",
          "ShipDate": "2026-01-15T10:30:00-06:00",
          "TrackingNumber": "SYN-A1B2C3D4E5F6",
          "TrackingUrl": "https://www.fedex.com/fedextrack/?trknbr=SYN-A1B2C3D4E5F6",
          "Weight": 2.95
        }
      ],
      "OrderNumber": 12345678,
      "Event": "Shipped",
      "ConfirmationId": "11111111-1111-1111-1111-111111111111",
      "EntryId": "sample-entry-id",
      "Reference": "SAMPLE-REFERENCE",
      "SequenceNumber": "1"
    }
  },
  {
    "slug": "Processed",
    "available": true,
    "sample": {
      "Status": "Accepted",
      "Errors": [],
      "OrderNumber": 12345678,
      "Event": "Processed",
      "ConfirmationId": "11111111-1111-1111-1111-111111111111",
      "EntryId": "sample-entry-id",
      "Reference": "SAMPLE-REFERENCE",
      "SequenceNumber": "1"
    },
    "variants": ["Accepted", "Rejected"]
  },
  {
    "slug": "Order Cancelled",
    "available": true,
    "sample": {
      "Event": "Order Cancelled",
      "OrderNumber": 12345678,
      "ConfirmationId": "11111111-1111-1111-1111-111111111111",
      "EntryId": "sample-entry-id",
      "Reference": "",
      "SequenceNumber": "1"
    }
  },
  {
    "slug": "ImageDownload",
    "available": false,
    "reason": "No V2 payload exists for this event. Image failures are delivered as Processed with Status=Rejected.",
    "seeAlso": "Processed"
  }
]

The sample objects show the V2 payload shape, not a recording of a sandbox delivery. Two fields differ in what the sandbox actually sends you — see OrderNumber is absent and the sandbox marker below.

The three event slugs

Each slug is spelled exactly as the V2 wire Event string. Send the slug in the request body, not in the URL.

Slug Delivers
Shipped A shipment notification, with one ShippingInfo entry per shipment you ask for.
Processed A status notification. Two variants: Accepted and Rejected.
Order Cancelled A cancellation notification.

Order Cancelled contains a space, and the match is case-sensitive. order cancelled, OrderCancelled and order.cancelled are all rejected:

1
2
400 {"error":"invalid_event",
     "message":"\"event\" must be exactly one of: \"Shipped\", \"Processed\", \"Order Cancelled\"."}

ImageDownload is unavailable

Discovery lists ImageDownload with "available": false, and triggering it returns 400 event_unavailable. There is no V2 payload for that event, so there is nothing for the sandbox to send.

Image failures reach you as Processed with "Status": "Rejected" and a populated Errors[] array. That is the shape to code against. If you are working from the V1 format documentation, this is one of the places V2 differs.

POST /api/Webhooks/Sandbox/Trigger

Content-type: application/json

The trigger queues one notification and returns 202 Accepted. Delivery happens on the next dispatch pass, a few moments later, exactly as it does for a real order.

Trigger a Shipped

Shipped requires a non-empty shipments array. Each entry takes a carrier, an optional weight, and an optional ISO-8601 shipDate.

1
2
3
4
5
6
7
8
9
10
curl https://sandbox.apps.whcc.com/api/Webhooks/Sandbox/Trigger \
	-H "Authorization: Bearer 726670514499" \
	-H "Content-Type: application/json" \
	-d '{
	      "event": "Shipped",
	      "shipments": [
	        { "carrier": "FedEx", "weight": 2.95, "shipDate": "2026-01-15T10:30:00-06:00" }
	      ]
	    }' \
	-X POST
Copy to Clipboard

Example Response

1
202 {"notifyUid":12203823,"event":"Shipped","orderNumber":6563908}

An empty or missing shipments array returns 400 shipments_required. A synthetic Shipped with no shipment rows cannot build a payload, so the trigger rejects it rather than queueing a notification that fails later.

Trigger a Processed

Processed takes an optional status. It defaults to Accepted. The value is matched case-insensitively, and anything other than Accepted or Rejected returns 400 invalid_status.

Accepted

1
2
3
4
{
	"event": "Processed",
	"status": "Accepted"
}

Rejected

Rejected requires a non-empty errors array. Each entry takes an errorCode, an error message, and an assetPath.

1
2
3
4
5
6
7
8
9
10
11
{
	"event": "Processed",
	"status": "Rejected",
	"errors": [
		{
			"errorCode": "400.13",
			"error": "synthetic image failure",
			"assetPath": "https://example.test/a.jpg"
		}
	]
}

That request delivers this payload to your callback URI:

1
2
3
4
5
{"Status":"Rejected",
 "Errors":[{"ErrorCode":"400.13","Error":"synthetic image failure",
            "AssetPath":"https://example.test/a.jpg"}],
 "Event":"Processed","ConfirmationId":"…","EntryId":"SANDBOX-550",
 "Reference":"SANDBOX-550","SequenceNumber":"1"}

Omitting errors on a Rejected returns 400 errors_required. Omitting errorCode on an entry returns 400 error_code_required.

Trigger an Order Cancelled

Order Cancelled takes no other fields.

1
2
3
{
	"event": "Order Cancelled"
}

OrderNumber is absent from sandbox payloads

Sandbox payloads have no OrderNumber field at all. It is not null — it is absent, because the field is omitted rather than serialized when no order number exists. The order WHCC seeds for the sandbox never receives one.

A parser that treats OrderNumber as required will fail on every sandbox webhook while succeeding on every real one. Make the field optional in your handler before you start testing.

Note also that the orderNumber in the 202 response is not the number the payload will carry, because the payload carries none. Treat it as an identifier for the staging record, not as an order number.

Correlate a trigger with its delivery on notifyUid. That value is returned in the 202 and is the only field that ties the two together.

The sandbox marker

Both EntryId and Reference read SANDBOX-<ConsumerUID> on every synthetic payload — SANDBOX-550 in the example above, where 550 is the consumer ID of the credentials that triggered it.

This is deliberate. It lets you tell a synthetic webhook from a real one at a glance, in a log or in a handler, without checking anything else.

Tracking numbers are generated, not supplied

WHCC generates the tracking number for every synthetic shipment. The format is SYN- followed by 12 uppercase hexadecimal characters:

1
"TrackingNumber": "SYN-078F1F4772F7"

That format matches no real carrier, so a sandbox tracking number can never collide with a real shipment. You cannot supply your own. The tracking and trackingUrl request fields were removed; sending them has no effect.

TrackingUrl is derived from the carrier you asked for:

1
"TrackingUrl": "https://www.fedex.com/fedextrack/?trknbr=SYN-078F1F4772F7"

The tracking link will not resolve at the carrier. The URL is well-formed and points at the real carrier site, but the number is synthetic, so the carrier has no shipment to show. This is expected. Use the link to test that your handler stores and renders it, not to track anything.

Accepted carrier values

carrier is validated against five values:

  • UPS
  • USPS
  • UPS MI
  • FedEx
  • DHL GM

Input is matched case-insensitively, and the payload carries the canonical spelling above rather than the casing you sent. Send fedex and the payload reads FedEx.

Anything else is rejected:

1
2
400 {"error":"invalid_carrier",
     "message":"\"carrier\" must be one of: UPS, USPS, UPS MI, FedEx, DHL GM."}

errorCode is limited to 6 characters

Real values are six-character WHCC codes — 400.01, 400.02, 400.03, 400.08, 400.13, 400.17. See Errors for the full list.

A longer value is rejected. Sending an invented code such as IMG_DOWNLOAD_FAILED returns:

1
2
400 {"error":"invalid_error_code",
     "message":"\"errorCode\" must be at most 6 characters; real values are six-character WHCC codes such as 400.13."}

What's Next

Event Types documents the full V2 payload for each event.

Delivery, Retries & Idempotency covers the retry schedule, the response timeout, and why your handler must deduplicate.

Back to Top 👆
Get in Touch

Interested in integrating with WHCC? Tell us more about what you’re looking for and we’ll be in touch.