Skip to content
← Blog · · 14 min read · WooCommerce

WooCommerce USPS Plugin Broke?
Here's the Complete v3 Fix

If your WooCommerce USPS shipping rates vanished overnight, you're not alone. USPS retired the Web Tools XML API that every WooCommerce shipping plugin depended on. The official WooCommerce USPS extension still hasn't shipped a v3 update. This guide gives you working PHP code to restore live USPS rates in your store today.

Why WooCommerce USPS Plugins Stopped Working

Every WooCommerce USPS shipping plugin built before 2025 uses the same underlying API: USPS Web Tools, an XML-based SOAP interface at secure.shippingapis.com. USPS has been deprecating this API since 2024, and in 2026 the restrictions hit critical mass:

  • New USERID registrations blocked — USPS no longer issues Web Tools credentials. If you're setting up a new store, you can't even get started.
  • RateV4 endpoint failures — Existing stores see intermittent timeouts, empty responses, and SOAP faults from the legacy pricing endpoint.
  • TrackV2 deprecated — Tracking lookups via the old XML interface return stale or empty data.

Affected plugins — if you use any of these, your shipping rates are broken or about to break:

Plugin Status
WooCommerce Shipping USPS (by WooCommerce) No v3 update shipped
USPS Shipping Method (by Jeril Flavor) Abandoned — last update 2023
WC USPS Shipping Calculator Removed from wp.org
Stamps.com API-based plugins Stamps.com → Auctane migration broke integrations
ShipStation for WooCommerce Works (uses their own API), but no direct USPS rates at checkout

Here's what the old plugin code looks like under the hood — and why it's failing:

Legacy Plugin Internals (broken)
// WooCommerce Shipping USPS (by WooCommerce)
// This is what broke — XML Web Tools call inside
// includes/class-wc-shipping-usps.php

$request = '<RateV4Request USERID="'
  . $this->user_id . '">';
$request .= '<Package ID="0">';
$request .= '<Service>ALL</Service>';
$request .= '<ZipOrigination>'
  . $origin . '</ZipOrigination>';
// ... 40 more lines of XML ...

$response = wp_remote_post(
  'https://secure.shippingapis.com'
  . '/ShippingAPI.dll?API=RateV4',
  [ 'body' => $request ]
);
// Returns: HTTP 200 with XML error body
// or: Connection refused / timeout

The Official Fix (And Why It's Incomplete)

WooCommerce's official USPS extension ($79/year) has not been updated to use the v3 REST API. Their GitHub issues page has requests dating back to late 2024 with no resolution. The community alternatives are worse — most are abandoned.

The replacement API exists and is stable. USPS ships a well-documented REST API at apis.usps.com with OAuth 2.0, JSON responses, and proper HTTP semantics. The gap is integration: nobody has built a WooCommerce shipping method that uses it.

You have two options. Both involve replacing the plugin with a custom WC_Shipping_Method class:

  • 1. Direct USPS v3 integration — Full control. You manage OAuth tokens, CRID registration, and the 60 req/hr rate limit.
  • 2. RevAddress drop-in — Same WC_Shipping_Method, but calling our API instead. No OAuth, no CRID, no rate limit headaches. One API key.

Option 1: Direct USPS v3 REST Integration

This is the full WC_Shipping_Method class that replaces any broken USPS plugin. It handles OAuth token management, calls the v3 prices endpoint, and registers rates at checkout. Drop it into a custom plugin or your theme's functions.php.

Prerequisites: A USPS Developer Portal account at developer.usps.com with a Client ID and Client Secret. You also need a CRID (Customer Registration ID) — see our CRID enrollment guide.

PHP — Complete WC_Shipping_USPS_V3 Class
// Drop this in your theme's functions.php
// or a custom plugin file

add_action('woocommerce_shipping_init', function() {

class WC_Shipping_USPS_V3 extends WC_Shipping_Method {

  private string $token = '';
  private int $token_expires = 0;

  public function __construct($instance_id = 0) {
    $this->id = 'usps_v3';
    $this->instance_id = absint($instance_id);
    $this->method_title = 'USPS (v3 REST)';
    $this->method_description
      = 'Live USPS rates via v3 REST API.';
    $this->supports = [
      'shipping-zones',
      'instance-settings',
    ];
    $this->init();
  }

  private function init() {
    $this->init_form_fields();
    $this->init_settings();
    $this->title = $this->get_option('title');
    $this->enabled
      = $this->get_option('enabled');
  }

  // OAuth token with caching
  private function get_token(): string {
    if ($this->token
        && time() < $this->token_expires) {
      return $this->token;
    }

    $resp = wp_remote_post(
      'https://apis.usps.com/oauth2/v3/token',
      [
        'body' => [
          'grant_type'    => 'client_credentials',
          'client_id'     => $this->get_option('client_id'),
          'client_secret' => $this->get_option('client_secret'),
          'scope'         => 'prices tracking',
        ],
      ]
    );

    $data = json_decode(
      wp_remote_retrieve_body($resp)
    );
    $this->token = $data->access_token;
    $this->token_expires
      = time() + $data->expires_in - 300;

    return $this->token;
  }

  // Fetch live rates from USPS v3
  public function calculate_shipping(
    $package = []
  ) {
    $dest_zip = $package['destination']['postcode'];
    $origin   = $this->get_option('origin_zip');
    $weight   = wc_get_weight(
      WC()->cart->get_cart_contents_weight(),
      'oz'
    );

    $resp = wp_remote_post(
      'https://apis.usps.com'
      . '/prices/v3/total-rates/search',
      [
        'headers' => [
          'Authorization'
            => 'Bearer ' . $this->get_token(),
          'Content-Type'
            => 'application/json',
        ],
        'body' => wp_json_encode([
          'originZIPCode'      => $origin,
          'destinationZIPCode' => $dest_zip,
          'weight'             => $weight,
          'mailClass'          => 'ALL',
          'processingCategory'
            => 'MACHINABLE',
        ]),
      ]
    );

    $body = json_decode(
      wp_remote_retrieve_body($resp)
    );

    if (!$body || !isset($body->rateOptions)) {
      return;
    }

    foreach ($body->rateOptions as $opt) {
      $this->add_rate([
        'id'    => $this->id . '_'
                   . sanitize_title($opt->mailClass),
        'label' => $opt->mailClass,
        'cost'  => $opt->totalPrice,
      ]);
    }
  }
}

});

// Register the shipping method
add_filter(
  'woocommerce_shipping_methods',
  function($methods) {
    $methods['usps_v3']
      = 'WC_Shipping_USPS_V3';
    return $methods;
  }
);

After adding this code, go to WooCommerce → Settings → Shipping → Shipping Zones, edit your zone, and add "USPS (v3 REST)" as a shipping method. Enter your Client ID, Client Secret, and origin ZIP in the instance settings.

Option 2: RevAddress Drop-In

The direct integration works, but it comes with real operational costs: OAuth token lifecycle, CRID/MID registration (which takes days), the 60 req/hr rate limit, and debugging USPS's error responses. RevAddress handles all of that behind a single API key.

Replace the get_token() and rate-fetching logic with this:

Direct USPS v3
  • OAuth 2.0 token management
  • CRID/MID registration required
  • 60 req/hr rate limit
  • Debug USPS error formats
RevAddress API
  • Single API key (no OAuth)
  • No CRID registration
  • 600 req/min (built-in caching)
  • Clean JSON error messages
PHP — RevAddress Rate Lookup (replaces get_token + wp_remote_post to USPS)
// Same WC_Shipping_Method — but via RevAddress
// No OAuth, no CRID, no rate limits

private function get_rates(
  string $origin,
  string $dest,
  float $weight
): array {
  $resp = wp_remote_post(
    'https://api.revaddress.com/v1/rates',
    [
      'headers' => [
        'Authorization'
          => 'Bearer ' . $this->get_option('api_key'),
        'Content-Type'
          => 'application/json',
      ],
      'body' => wp_json_encode([
        'originZip'      => $origin,
        'destinationZip' => $dest,
        'weight'         => $weight,
      ]),
    ]
  );

  return json_decode(
    wp_remote_retrieve_body($resp),
    true
  )['rates'] ?? [];
}
// That's it. No OAuth dance. No token refresh.
// No CRID registration. No 60 req/hr limit.

Rate Shopping with v3 API

The v3 prices endpoint returns all available USPS services for a given package. The key difference from the old RateV4: service names changed. Your checkout labels need to map the new mailClass values to human-readable names.

PHP — Service Name Mapping
// Map USPS v3 mailClass to WooCommerce display names
const USPS_SERVICE_MAP = [
  'PRIORITY_MAIL_EXPRESS'
    => 'USPS Priority Mail Express (1-2 days)',
  'PRIORITY_MAIL'
    => 'USPS Priority Mail (1-3 days)',
  'USPS_GROUND_ADVANTAGE'
    => 'USPS Ground Advantage (2-5 days)',
  'FIRST_CLASS_MAIL'
    => 'USPS First-Class Mail (3-5 days)',
  'MEDIA_MAIL'
    => 'USPS Media Mail (2-8 days)',
];

// In calculate_shipping(), replace the label:
'label' => USPS_SERVICE_MAP[$opt->mailClass]
  ?? $opt->mailClass,
Old RateV4 Name v3 mailClass
Priority Mail Express<tm> PRIORITY_MAIL_EXPRESS
Priority Mail<tm> PRIORITY_MAIL
First-Class Mail<tm> FIRST_CLASS_MAIL
USPS Retail Ground<tm> USPS_GROUND_ADVANTAGE
Media Mail MEDIA_MAIL

Note: "USPS Retail Ground" was replaced by "USPS Ground Advantage" in July 2023. If your old plugin still shows "Retail Ground" or "Parcel Select" as options, those service names are gone in v3. Ground Advantage is the unified replacement.

Tracking Integration

If you're generating labels outside WooCommerce (via Pirate Ship, USPS.com, or any label printer), you can still pull tracking data into WooCommerce order notes using the v3 tracking endpoint.

PHP — Tracking Hook for WooCommerce Orders
// Hook into order completion to fetch tracking
add_action(
  'woocommerce_order_status_completed',
  function($order_id) {
    $order = wc_get_order($order_id);
    $tracking = $order->get_meta('_tracking_number');

    if (! $tracking) return;

    // Get OAuth token (use the same get_token() from above)
    $token = usps_v3_get_token();

    $resp = wp_remote_get(
      'https://apis.usps.com/tracking/v3/tracking/'
      . $tracking,
      [
        'headers' => [
          'Authorization'
            => 'Bearer ' . $token,
        ],
      ]
    );

    $data = json_decode(
      wp_remote_retrieve_body($resp)
    );

    if (isset($data->trackingNumber)) {
      $order->add_order_note(
        sprintf(
          'USPS Status: %s | %s',
          $data->statusCategory,
          $data->status
        )
      );
    }
  }
);

This hook fires when an order transitions to "Completed." It reads the _tracking_number meta field (set by most shipping label plugins), hits the v3 tracking endpoint, and adds the latest status as an order note visible to the customer.

Common Migration Errors

These are the errors WooCommerce store owners hit most frequently during the USPS migration:

Error Cause Fix
SOAP-ENV:Server Web Tools XML endpoint failing Migrate to v3 REST (this guide)
401 Unauthorized Bad Client ID/Secret or expired token Verify credentials at developer.usps.com. Check Content-Type: application/x-www-form-urlencoded on token request.
429 Too Many Requests Exceeded 60 req/hr limit Cache rate lookups with WP transients. Or use RevAddress (600 req/min).
403 Forbidden API product not subscribed or CRID not linked In developer.usps.com, confirm "Domestic Prices" and "Tracking" are in your app's API products.
Empty rates array Missing required fields or invalid ZIP Ensure weight, mailClass, and processingCategory are all present in the request body.
"No rates" at checkout Shipping method not added to zone WooCommerce → Settings → Shipping → edit your zone → add the new USPS v3 method.
cURL error 28 USPS v3 endpoint timeout Add 'timeout' => 15 to wp_remote_post args. USPS v3 is slower than legacy on first call (cold OAuth).

Migration Checklist

Go from broken shipping rates to live USPS v3 in your WooCommerce store:

  1. 1 Deactivate the old USPS plugin — Don't delete it yet. Deactivate in Plugins → Installed Plugins so it stops making Web Tools calls.
  2. 2 Register at developer.usps.com — Create a new application. Note your Client ID and Client Secret. Subscribe to "Domestic Prices" and "Tracking" API products.
  3. 3 Get a CRID — Register at USPS Business Customer Gateway (gateway.usps.com). Your CRID is issued automatically. See our CRID enrollment guide for the full walkthrough.
  4. 4 Add the shipping method code — Copy the WC_Shipping_USPS_V3 class from Option 1 or the RevAddress version into a custom plugin file.
  5. 5 Configure the shipping zone — WooCommerce → Settings → Shipping → edit your zone → Add shipping method → select "USPS (v3 REST)." Enter credentials in instance settings.
  6. 6 Test checkout — Add a product to cart, enter a US shipping address, and verify rates appear. Check the WooCommerce → Status → Logs for any API errors.
  7. 7 Add caching — For direct USPS integration, wrap rate lookups in set_transient() / get_transient() with a 1-hour TTL. This keeps you under the 60 req/hr limit.
  8. 8 Delete the old plugin — Once v3 rates are confirmed working in production, remove the deactivated legacy plugin entirely.

How Urgent Is This?

USPS has not published a hard shutdown date for Web Tools, but the trajectory is clear:

  • 2024 USPS launched v3 REST API. Stopped issuing new Web Tools credentials.
  • 2025 Web Tools documentation moved to "legacy." Rate limit enforcement tightened on old endpoints.
  • 2026 "API Access Control" initiative restricting legacy endpoint access. Intermittent failures increasing.

Every week you wait, you risk checkout failures where customers see "$0.00 shipping" or no shipping options at all. Both cost you sales. Migrate now.

Don't want to manage USPS OAuth and rate limits?

RevAddress gives you one API key for USPS rates, tracking, address validation, and labels. No CRID registration, no token refresh, no 60 req/hr limit. Drop it into WooCommerce in 10 minutes.