FedEx migrated its developer platform from the legacy SOAP-based Web Services to the modern FedEx REST API a few years back, and every FedEx integration I inherit from a client's previous developer is still running against the old SOAP endpoints, quietly accumulating deprecation risk. If you're building this fresh, start on the REST API, the OAuth flow alone is worth the switch.
OAuth client credentials, scoped per environment
FedEx's REST API uses OAuth 2.0 client credentials grant, no user context, just a client ID and secret tied to your FedEx developer account, exchanged for a bearer token good for about an hour:
var req = new HttpRequestMessage(HttpMethod.Post, $"{_baseUrl}/oauth/token")
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = _settings.ClientId,
["client_secret"] = _settings.ClientSecret,
})
};
var resp = await _httpClient.SendAsync(req);
var token = JsonSerializer.Deserialize<FedExTokenResponse>(await resp.Content.ReadAsStringAsync());
// token.expires_in is seconds, cache and refresh proactively, not on 401
FedEx has separate sandbox and production base URLs with entirely separate credentials, a mistake I see constantly is a client's staging Acumatica instance accidentally pointed at production FedEx credentials during testing, which creates real tracking numbers and, worse, can trigger real label charges if the integration also does label generation.
Where a tracking number lands on the Shipment DAC
I attach FedEx integration at the point a Shipment is confirmed in Acumatica, using a RowPersisted handler on SOShipment gated on the carrier field, rather than trying to call FedEx synchronously inside RowPersisting, an external HTTP call inside a persisting handler risks the whole save failing or timing out because of a slow third-party response, which is exactly the kind of coupling you don't want on your core shipment save path.
protected virtual void _(Events.RowPersisted<SOShipment> e)
{
if (e.Row == null || e.TranStatus != PXTranStatus.Completed) return;
if (e.Row.UsrCarrier != "FEDEX" || !string.IsNullOrEmpty(e.Row.UsrTrackingNbr)) return;
// Queue, don't call inline, a scheduled process or PXLongOperation
// picks this up and calls FedEx's Ship API asynchronously
PXLongOperation.StartOperation(Base, () => FedExShipmentService.CreateShipment(e.Row.ShipmentNbr));
}
Pulling tracking status: webhooks exist, but polling is the fallback that actually ships
FedEx offers tracking event notifications via its Track API's subscription mechanism, but in my experience the setup friction (a public HTTPS endpoint, FedEx-side subscription approval, payload signature verification) means most mid-size Acumatica clients end up polling the Track API on a schedule instead, every 30-60 minutes for active shipments, tapering off once a shipment reaches "Delivered." I keep a small custom DAC tracking which shipments are still "in flight" so the scheduled process only polls active ones rather than re-querying delivered shipments from three months ago forever.
FedEx's Track API enforces per-minute rate limits that are easy to hit if you loop through hundreds of open shipments naively. Batch tracking numbers into the multi-tracking-number request the API supports (up to 30 per call) instead of one HTTP call per shipment. This alone took one client's nightly tracking sync from a 45-minute run that occasionally got throttled mid-batch down to under five minutes.
Mapping FedEx status codes to something Acumatica users understand
FedEx's tracking events use their own status code vocabulary (OC, PU, IT, DL, and dozens more) that means nothing to a customer service rep looking at a Sales Order. I maintain a lookup DAC mapping FedEx's derived status codes to a small internal enum (Booked, In Transit, Out for Delivery, Delivered, Exception) and surface only that internal enum on the Sales Order screen, keeping the raw carrier payload available in a related detail view for anyone who needs to dig deeper on a support call.
Wrapping up
A solid FedEx integration for Acumatica means moving to the REST API if you haven't already, keeping the shipment creation call asynchronous and off the RowPersisting save path, polling tracking in efficient multi-number batches rather than one call per shipment, and translating FedEx's own status vocabulary into something your operations team can actually read at a glance. None of it is exotic engineering, it's mostly discipline about where the external call sits relative to Acumatica's transaction boundary.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.