---
title: "AfterShip"
space: "BWH Shipping"
url: "https://docs.bwh.tech/bwh-shipping/carriers/aftership"
updated: "2026-09-21"
---

Buy labels and track parcels across hundreds of carriers worldwide through [AfterShip Shipping](https://www.aftership.com/shipping). You connect your own carrier accounts in AfterShip, and BWH Shipping rate-shops across them.

## Before you start

- An AfterShip account with **Shipping** and **Tracking**, and at least one carrier account connected
- An **API key**. Sandbox and production keys are different.

## Set up AfterShip

**1. Fill in AfterShip Shipping Settings**

| Field | Value |
|---|---|
| API Key | Your AfterShip API key, matching **Test Mode** |
| Test Mode | On by default: labels are bought on the sandbox host. Tracking always uses AfterShip's live Tracking host, which has no sandbox. Untick it to go live. |
| Shipper Account ID | Optional. Leave it blank to rate-shop every carrier account on the key. |
| Pickup Address | An ERPNext Address to ship from. Checkout quotes from it. |
| Currency | Used when a rate has no currency |
| Volumetric Divisor | `5000`, unless your carriers say otherwise |
| Rates Timeout (seconds) | How long checkout waits for rates. Default: 10. |
| Webhook Secret | The signing secret from AfterShip's webhook settings. See step 3. |

The host and API-version fields are pre-filled. Leave them alone unless AfterShip tells you otherwise.

**2. Create a Shipping Provider Profile** named `AfterShip`, set **Provider Settings** to `AfterShip Shipping Settings`, and tick **Enabled**.

**3. Add the webhook** in AfterShip's Tracking webhook settings:

- **URL:** `https://your.site/api/v2/method/bwh_shipping.bwh_shipping.webhook.handle?provider=AfterShip`

Copy the webhook's signing secret into **Webhook Secret**.

**4. Import Shipping Services.** In the **Shipping Service** list, click **Import from Carrier** and pick services from your connected accounts. It needs an enabled profile and a Pickup Address with a country. Each service's code has the form `<shipper account id>|<service type>`.

To check your key, run this in `bench console`:

```python
frappe.get_single("AfterShip Shipping Settings").test_connection()
```

## How it behaves

- **Rates** come from every connected account, or from **Shipper Account ID** if you set it. Services without a price are dropped.
- **Cash on delivery isn't supported.** COD carts still get AfterShip rates, and a COD shipment is booked as a prepaid label. Don't offer AfterShip services for COD orders.
- **Booking** buys the label in one call, then registers the parcel for tracking. If registering tracking fails, the label is still kept.
- **Tracking** needs that registration. A shipment without it reports no status.
- **Cancelling** cancels the label.
- **Pickups** and **manifests** aren't available through AfterShip.

| AfterShip tag | Shipping Request status |
|---|---|
| Pending, InfoReceived | Ready To Ship |
| InTransit | In Transit |
| OutForDelivery, AvailableForPickup | Out For Delivery |
| AttemptFail | Undelivered |
| Delivered | Delivered |

`Exception` and `Expired` are ignored, so the status stays as it was. On **Sync Status**, their checkpoints are still added as tracking events.

## Good to know

- **Webhooks are signed** with HMAC-SHA256, in the `Aftership-Hmac-Sha256` header, and each delivery is checked against **Webhook Secret**.
- Countries are sent to AfterShip as ISO alpha-3 codes, converted with `pycountry`, which ships with Frappe.
- **Enabled** in these settings isn't used. The profile's **Enabled** switches the carrier on.
- Addresses are sent as residential.

## Next steps

- [Use it in your app](/bwh-shipping/get-started/use-it-in-your-app): quote at checkout and book shipments
- [Webhooks and security](/bwh-shipping/build/webhooks-and-security): debug a delivery that isn't arriving
