App guides

The AfterShip app sends your Swell shipments to AfterShip as trackings, and brings their delivery status back to the order. When a shipment gets a tracking number, AfterShip starts tracking the parcel with the carrier and, if you've set them up in AfterShip, sends its own tracking notifications to the customer.

Status changes reach Swell through a signed AfterShip webhook, and the app also refreshes trackings still in transit every hour. Each order's AfterShip tab shows the delivery status, estimated delivery and latest scan of its shipments.

WhenWhat the app does
A shipment is created, or its tracking number or carrier changesCreates an AfterShip tracking with the order, destination and customer, if Send shipments to AfterShip is on. A new tracking number or carrier replaces the old tracking.
A shipment is canceled or deletedDeletes its AfterShip tracking, if Delete trackings of canceled shipments is on.
AfterShip sends a tracking update to the webhookChecks the signature, loads the tracking from AfterShip, and saves its status on the shipment and on the order's AfterShip tab.
Every hourReloads up to 50 trackings that aren't delivered or expired, from shipments created in the last 60 days.
You run Send existing shipmentsSends the shipments from the last 7, 30 or 90 days that the app hasn't sent yet, in the background.
You run Test connectionChecks the API key, lists the couriers turned on in AfterShip, and says whether the webhook secret is set.

Nothing is sent to AfterShip while Enabled is off.

  • An AfterShip account on a plan that includes API access.
  • An AfterShip API key with the API key authentication type. AES and RSA signature keys aren't supported. A key with the default permissions works. If you restrict it, allow reading and writing trackings. You create the key in step 1.
  • Your AfterShip webhook secret, from Settings > Webhooks in AfterShip. Without it, the app rejects every webhook, and statuses reach Swell only with the hourly refresh.
  • A Swell public key for each environment, from Developer > API keys, to build the webhook URL.

Install AfterShip from the Swell App Store, and set it up in your test environment before the live one.

1
Create an API key

In AfterShip, go to Settings > API keys and click Create API key. Enter a name, choose the API key authentication type, and keep the default permissions, or allow reading and writing trackings. Save the key and copy it.

2
Copy the webhook secret

In AfterShip, go to Settings > Webhooks and copy the webhook secret.

3
Add them to the app

In Swell, go to Apps > AfterShip > Settings and enter the API key. Enabled is on by default. On the Tracking updates tab, enter the Webhook secret. On the Shipments tab, choose whether to share customer contact details and delete the trackings of canceled shipments, and set the Tags. Click Save settings.

4
Test the connection

Choose Actions > Test connection. The message starts with "Connected to AfterShip." and lists the couriers turned on in AfterShip, then says "Webhook secret is set." If the message reports an error, see Troubleshooting below.

5
Add the webhook in AfterShip

Start from the Webhook URL on the Tracking updates tab:

https://{store-id}:{public-key}@{store-id}.swell.store/functions/aftership/aftership-webhook

Replace {store-id}, in both places, with your store ID, the subdomain of your swell.store address, and {public-key} with a Swell public key from Developer > API keys: the test key for the test environment, the live key for live. Never use a secret key here.

In AfterShip, go to Settings > Webhooks, add the URL, and choose the tracking updates to send to it. Save the webhook secret in Swell first: until it's saved, the app answers every webhook with a 401 error.

6
Send existing shipments

To send shipments created before you installed the app, choose Actions > Send existing shipments, choose 7, 30 or 90 days, and click Send shipments. The run shows on the app's Workflows tab as Import shipments. Skip this step if you don't need past shipments in AfterShip.

7
Test with a shipment

On a test order, add a shipment with a real tracking number. AfterShip shows a tracking titled with the order number, such as "Order #1042", with the swell tag. On the order, the AfterShip tab shows the Delivery status and a line for the shipment under Shipments. The logs show "AfterShip: shipment … sent as tracking …".

When AfterShip has a status change for the parcel, it sends it to the webhook, and the tab shows the new status. The logs show "AfterShip: shipment … is" followed by AfterShip's status.

8
Go live

Settings are saved separately for each environment. In the live environment, repeat steps 3 to 5, and build the webhook URL with your live public key. If both environments use the same AfterShip account, add both URLs in AfterShip. Each environment ignores updates for trackings that none of its shipments has.

With Send shipments to AfterShip on, the app sends a shipment when it's created with a tracking number, and when its tracking number or carrier changes. Drafts, canceled shipments and shipments without a tracking number aren't sent. A shipment without a tracking number is sent once it gets one. Changes to other shipment details, such as notes or items, aren't sent.

Each tracking has:

  • Tracking number: the shipment's tracking number, or the first package's when the shipment has none.
  • Courier: from the shipment's carrier. See Couriers below.
  • Title and order: "Order #" and the order number as the title, with the order's ID, number and date.
  • Ship date: when the shipment was created.
  • Destination: the country, state, city and postal code of the shipment's destination, or of the order's shipping address.
  • Tags: the Tags setting, swell by default.
  • Customer: with Share customer contact on, the customer's name, email and phone number, when there's an email or a phone number. The email is the account's, or the billing email. Phone numbers are sent in international format, such as +15035550142, and left out when they aren't valid.
  • Custom fields: the Swell shipment ID and order ID, which the app uses to find the shipment when AfterShip sends an update.

If AfterShip already has a tracking with the same number, for example one you added in AfterShip, the app links the shipment to it instead of creating a second one.

When the tracking number or carrier changes, the app creates a new tracking and deletes the old one. If AfterShip refuses a shipment, for example because the tracking number isn't valid, the reason shows on the order's AfterShip tab, and the shipment isn't sent again until its tracking number or carrier changes.

The app sends the shipment's carrier as the AfterShip courier, in lowercase with spaces and underscores turned into hyphens. Carriers whose AfterShip name differs are translated, such as dhl_express to dhl, canada_post to canada-post and royal_mail to royal-mail. Carriers set by a shipping app use the carrier name instead.

When the shipment has no usable carrier, or AfterShip doesn't accept the courier, the tracking is sent without one, and AfterShip detects the courier from the tracking number. Detection works best with the couriers you ship with turned on in AfterShip. If AfterShip picks the wrong courier, correct it in AfterShip.

AfterShip sends status changes to the webhook. The app loads the tracking from AfterShip with your API key, then saves its status, latest scan with its location, estimated delivery, delivery date and tracking page on the shipment, and updates the order's AfterShip tab. Updates older than the one already saved are ignored.

The statuses are AfterShip's: Pending, Info received, In transit, Out for delivery, Delivery attempt failed, Ready for pickup, Delivered, Exception and Expired. The app changes only its own fields. The order's status and the shipment's own details stay as they are.

Each order gets an AfterShip tab with these read-only fields:

FieldWhat it shows
Delivery statusThe status all the order's shipments share. When they differ: Exception or Delivery attempt failed if any shipment has it, else Partly delivered (n of m) when some are delivered, else the first status that isn't Pending.
Estimated deliveryThe latest estimate of the shipments not delivered yet, as AfterShip gives it, a date or a date and time.
DeliveredWhen the last shipment was delivered, once all of them are.
Tracking pageThe AfterShip tracking page of the first shipment not delivered yet, or of the shipment updated last.
ShipmentsOne line per shipment: the shipment number, courier and tracking number, status, latest scan and tracking page.

A shipment AfterShip refused, or whose tracking was deleted in AfterShip, shows "Not tracked:" with the reason instead of a status. Canceled shipments and shipments the app hasn't sent aren't listed.

Every hour, at 15 minutes past, the app reloads up to 50 trackings from AfterShip, those checked longest ago first, in case a webhook was missed. It checks shipments created in the last 60 days that aren't canceled, while their status isn't Delivered or Expired. Webhooks still update the shipment after that. There's no action to refresh right away.

If a tracking was deleted in AfterShip, the refresh removes it from the shipment, and the order's AfterShip tab shows "Not tracked: The tracking was deleted in AfterShip." The shipment isn't sent again until its tracking number or carrier changes.

With Delete trackings of canceled shipments on, canceling or deleting a shipment in Swell deletes its AfterShip tracking, so AfterShip stops tracking the parcel and sending notifications for it. A tracking that's already gone in AfterShip counts as deleted. This also deletes a tracking you created in AfterShip that the shipment was linked to.

A canceled shipment's tracking is cleared from it, so if the shipment is restored, it's sent again. With the setting off, the tracking stays in AfterShip.

To send shipments created before the app was installed, or while sending was off, go to Apps > AfterShip, choose Actions > Send existing shipments, choose 7 days, 30 days or 90 days in Shipments created in the last, and click Send shipments.

  • The run sends each shipment the same way as a shipment event, with the current settings, 50 shipments at a time, 3 at once. Canceled shipments aren't loaded.
  • Runs are listed on the app's Workflows tab as Import shipments, with their status. Click a run to see its steps.
  • When it finishes, the logs show the totals: sent, linked to existing trackings, already sent, skipped and failed. Shipments AfterShip refused before count as already sent until their tracking number or carrier changes. Drafts, shipments without a tracking number and shipments whose order isn't found count as skipped.
  • Running it again doesn't create duplicates. Shipments already sent are counted as already sent.
  • If Enabled or Send shipments to AfterShip is off, or the API key is empty, the run doesn't start and fails with the reason. The settings are read again before each 50 shipments, so turning one off during a run stops it.
  • If AfterShip can't be reached, rate-limits the app or has a server error, each group of 50 is tried up to 5 more times before the run fails.

Send existing shipments is in the Actions menu only while Send shipments to AfterShip is on.

WhereWhat it's for
Apps > AfterShip > SettingsThe API key, Enabled, and the Shipments and Tracking updates tabs, with the webhook secret and the webhook URL pattern.
Apps > AfterShip > ActionsTest connection, and Send existing shipments while Send shipments to AfterShip is on.
Apps > AfterShip > WorkflowsThe runs of Send existing shipments, listed as Import shipments, with their status.
An order's AfterShip tabDelivery status, Estimated delivery, Delivered, Tracking page and Shipments.
Developer > Console > LogsEach shipment sent or linked, each tracking deleted, rejected webhooks, and errors.

Settings are in Apps > AfterShip > Settings and are saved separately for each environment.

API key and Enabled are at the top. The other settings are on the Shipments and Tracking updates tabs.

SettingDescriptionDefault
API keyFrom AfterShip > Settings > API keys, with the API key authentication type. A restricted key needs to read and write trackings. Masked on the settings page. Required.None
EnabledTurns off everything the app sends to AfterShip, the hourly refresh and the webhook updates, without uninstalling it. Webhooks still need a valid signature. Test connection still works while it's off.On
Shipments > Send shipments to AfterShipCreates a tracking when a shipment gets a tracking number, and a new one when the number or carrier changes. Send existing shipments needs it.On
Shipments > Share customer contactSends the customer's name, email and phone number with each tracking, so AfterShip notifications can reach them.On
Shipments > Delete trackings of canceled shipmentsDeletes the AfterShip tracking when the shipment is canceled or deleted in Swell.On
Shipments > TagsComma-separated tags added to every tracking in AfterShip.swell
Tracking updates > Webhook secretFrom AfterShip > Settings > Webhooks. Webhooks without a valid signature are rejected, and while it's empty, every webhook is. Masked on the settings page.None
Tracking updates > Webhook URLRead only. The URL pattern to add in AfterShip, with your store ID and a public key filled in.The URL pattern
  • Each shipment is one tracking, with the shipment's tracking number or its first package's. Other package numbers aren't sent.
  • Only a new tracking number or carrier is sent. Later changes to the shipment, order or customer, such as a new address, don't reach AfterShip.
  • Customer notifications come from AfterShip, if you've set them up there. The app doesn't send emails.
  • Without the webhook, statuses reach Swell only with the hourly refresh, up to 50 trackings an hour. Trackings of shipments older than 60 days aren't refreshed.
  • If the test and live environments use the same AfterShip account and send the same tracking number, both shipments link to one tracking, and canceling or deleting either shipment deletes it.
  • A tracking deleted in AfterShip isn't sent again until the shipment's tracking number or carrier changes.
  • If AfterShip can't be reached, rate-limits the app or has a server error while a shipment is sent or deleted, Swell runs the function again later, at growing intervals. If it keeps failing for about 4 days, Swell turns off the function and emails the store's admins. Reinstall the app to turn it back on.
  • While AfterShip rejects the API key, webhooks are acknowledged without saving the update. The hourly refresh catches up once the key is fixed.

Test connection and Send existing shipments show their results in the dashboard, and errors about a shipment show on its order's AfterShip tab. The app also logs each shipment it sends and each error. In the Swell dashboard, the logs are under Developer > Console > Logs.

Enter the API key, click Save settings, and run Test connection again.

Copy the key again from Settings > API keys in AfterShip, and check that its authentication type is API key. AES and RSA signature keys aren't supported. Paste it, click Save settings, and test again.

The message names the call AfterShip refused. Check that the API key is allowed to read and write trackings, and that your AfterShip plan includes API access.

AfterShip had a server error. "Couldn't reach AfterShip (…). Try again later." means the request didn't get through, and "AfterShip rate limit reached (429). Try again in a minute." means AfterShip limited the app's requests. Try again in a minute.

Check that Enabled and Send shipments to AfterShip are on, and that the shipment has a tracking number and isn't a draft or canceled. Shipments created before the app was installed, or while sending was off, are sent when their tracking number or carrier changes, or with Send existing shipments. The logs show "AfterShip: missing API key in app settings" when the key is empty, and "not sent:" with the reason when AfterShip refused the shipment.

AfterShip refused the shipment, and the reason follows the message, for example an invalid tracking number. Fix the shipment's tracking number or carrier in Swell, and the app sends it again.

The tracking was deleted in AfterShip. To track the shipment again, change its tracking number or carrier in Swell, or add the tracking in AfterShip yourself.

Check that Enabled is on, and check the webhook in AfterShip under Settings > Webhooks: the URL needs your store ID and a public key for the same environment. Run Test connection to check that the webhook secret is set. Without webhooks, the status changes with the next hourly refresh, for shipments created in the last 60 days.

AfterShip's webhook gets a 401 response with this message while Webhook secret is empty. Copy the secret from Settings > Webhooks in AfterShip, enter it on the Tracking updates tab, and click Save settings.

AfterShip's webhook gets a 401 response with this message when the request isn't signed with the Webhook secret saved in Swell. Copy the secret again from AfterShip, save it, and check that the webhook is from the same AfterShip account as the API key.

AfterShip's webhook gets a 404 response with this message when the URL has no public key, a key from another store, or a key for an environment where the app isn't installed. Check the {store-id} and {public-key} parts of the URL. If your AfterShip plan includes custom webhook headers, you can instead add a header with the key Authorization and your Swell public key as its value.

The run fails without sending anything, with "Import not started: the AfterShip app is turned off in its settings." when Enabled is off, "Import not started: Send shipments to AfterShip is off in the app settings.", or "Import not started: add the API key in Apps → AfterShip." Fix the setting, click Save settings, and run it again.

Enabled or Send shipments to AfterShip was turned off, or the API key was removed, during the run. The message gives the reason and the totals until then. Fix the setting and run it again. Shipments already sent aren't sent twice.

AfterShip refused the run, for example because of a rejected API key or a 403. The message gives the same explanation as Test connection and the totals until then. Fix the cause and run it again.

AfterShip didn't accept any of the shipments. The reason for each is on its order's AfterShip tab, and the logs list each one with "not sent:".

Turn on Send shipments to AfterShip and click Save settings.

Uninstalling the app stops sending shipments, deleting the trackings of canceled shipments and refreshing statuses. It removes the app's functions and settings page, the AfterShip tab on orders, and the Test connection and Send existing shipments actions. The webhook stops working, so AfterShip's deliveries to it fail with a 404. Uninstalling applies to the environment you are in, so remove the app from the test and live environments separately.

The AfterShip values saved on your shipments and orders are kept, and show again if you reinstall the app. Trackings stay in AfterShip, and AfterShip keeps tracking them and sending its notifications. The app isn't notified when it's uninstalled, so delete the webhook in AfterShip under Settings > Webhooks, and the API key under Settings > API keys if you no longer need it. To pause the app without uninstalling it, turn off Enabled.

FunctionRuns onWhat it does
shipment-syncshipment.created, shipment.updated, shipment.canceled and shipment.deletedCreates or replaces the tracking on created and updated, and deletes it on canceled and deleted. Skips updates that change none of tracking_code, carrier, carrier_name, packages, canceled and draft.
aftership-webhookA public POST routeChecks the signature, reloads the tracking from AfterShip and saves it on its shipment and order.
refresh-trackingEvery hour, at minute 15Reloads up to 50 open trackings, least recently checked first, and saves their status.
test-connectionThe Test connection actionLists the couriers turned on in AfterShip and checks whether the webhook secret is set.
import-shipmentsThe Send existing shipments actionA workflow. Reads shipments 50 at a time in id order, one step per page, each step retried up to 5 times with exponential backoff from 30 seconds.

The app stores its fields on shipments under $app.aftership: tracking_id, synced, tracking_number, courier, status, status_detail, last_event, last_event_at, estimated_delivery, delivered_at, tracking_url, updated_at, checked_at and error. On orders, it stores aftership_status, aftership_tracking_url, aftership_estimated_delivery, aftership_delivered_at and aftership_shipments under $app.aftership.

Calls go to the AfterShip Tracking API version 2026-07, at https://api.aftership.com/tracking/2026-07, with the key in the as-api-key header:

  • POST /trackings creates the tracking. On meta code 4003, tracking already exists, the app looks it up with GET /trackings?tracking_numbers=…&slug=… and links the shipment to it. On 4010 or 4012, the courier slug was refused, it's sent again without slug.
  • GET /trackings/{id} for the webhook and the hourly refresh. Meta code 4004 means the tracking was deleted. A plain 404 isn't treated as deleted.
  • DELETE /trackings/{id} for canceled and deleted shipments, and for the old tracking when the number changes. A tracking that's already gone counts as deleted.
  • GET /couriers for Test connection.

The tracking body has tracking_number, slug, title, order_id, order_number, order_date, order_id_path, tracking_ship_date, destination_country_region, destination_state, destination_city, destination_postal_code, shipment_tags, custom_fields with swell_shipment_id and swell_order_id, and customers. Dates are sent as YYYY-MM-DDTHH:mm:ss+00:00, without milliseconds. order_id_path is the store's admin_url plus /orders/{id}, and is left out in Send existing shipments runs, whose requests have no admin_url.

The country is converted from ISO alpha-2 to alpha-3 with country-iso-2-to-3. Phone numbers are parsed with libphonenumber-js's parsePhoneNumberWithError, using the destination phone, then the order's shipping phone, then the account's phone, with the destination country as the default country. Numbers that fail to parse or isValid() are left out, and the rest are sent as E.164.

The webhook reads the aftership-hmac-sha256 header, the base64 HMAC-SHA256 of the raw request body keyed with the webhook secret, and compares it in constant time. It reads only msg.id from the payload, then loads the tracking with GET /trackings/{id}. The shipment is found by the tracking's custom_fields.swell_shipment_id when that shipment has the same tracking_id, else by $app.aftership.tracking_id. Updates whose updated_at is older than the saved one are skipped. Network errors, 429 and 5xx from AfterShip, and errors saving to Swell, answer 503. Other AfterShip errors are logged and acknowledged.

synced holds the courier slug and tracking number last sent, as usps:9400…, or auto: plus the number when there's no slug. A shipment is sent only when this key changes, and errors about the shipment are saved with the key, so they aren't retried on every update. In the event functions, network errors, 429 and 5xx are rethrown, so Swell retries the function. A retry is safe because an existing tracking is linked, not duplicated. 401 and 403 errors are logged and not saved.

The hourly refresh matches shipments by $app.aftership.status in the open statuses, canceled not true and date_created in the last 60 days, sorted by $app.aftership.checked_at. The import's step output holds only flags and counts, never the API key or webhook secret.

Swell builds and maintains the AfterShip app. For help with the app, email support@swell.is. For questions about your AfterShip account, couriers, trackings or notifications, contact AfterShip.