# Best practices

Hard-won recommendations for production integrations. Most of the pain
described below comes from real support tickets — save yourself the trip.

## Don't hardcode carriers and services

**Fetch the service list from the API instead of hardcoding it.** The set of
available carriers and services changes over time — carriers are added and
removed, the services available to *your account* depend on your contract,
and the catalogue also differs between the
[sandbox and production](/guides/testing). A hardcoded list silently goes
stale: you either offer services that fail at order time or miss carriers
you are already paying for.

Call `service_structure` ([SDK](/sdk): `getServiceStructure()`) to get the
current services, package types, options and pickup types for your account:

```php
$structure = $client->getServiceStructure();

foreach ($structure->getServices() as $service) {
    // Build your carrier selection UI / config from this list
    registerService($service['service_id'], $service['name'], $service['supplier']);
}
```

The structure changes rarely — cache it (e.g. refresh once a day or on
deployment) rather than calling it on every request, but treat the API as
the source of truth, never your code.

## Watch out: unknown payload keys are silently ignored

The API does not reject unknown fields in the `request` payload — a
misnamed key does not error, it just **silently drops your data** and the
API falls back to defaults. The classic trap: shipment dimensions are
`dimension1` / `dimension2` / `dimension3` (cm) — send `length` / `width` /
`height` and the order is created with default dimensions and a wrong
price, with no warning anywhere. Build payloads against the
[API v2 reference](/reference/v2-legacy) (or use the [SDK](/sdk)'s typed
request objects) and verify created orders with `getOrder()` during
development.

## Never trust the HTTP status code

The API returns **HTTP 200 for errors too** — the real outcome is the
`status` field in the response body. Check it on every call, or use the
[SDK](/sdk), which does it for you and throws `ApiException`. See
[Error semantics](/legacy/error-semantics).

## Be careful with retries

A timeout does not mean the request never reached the server. Blindly
retrying non-idempotent operations — `order_send` above all — can create
**duplicate orders** (and duplicate charges). Retry network failures only
for read operations, and reconcile writes via `getOrders()` before retrying
them. The SDK's `RetryingHttpClient` deliberately retries network failures
only, never API errors.

## Remember: prices are in groszy

Valuation and order prices are returned **in groszy** (1 PLN = 100 gr).
Divide by 100 before showing them to a human, and store them as integers —
floats and money end badly.

## Keep your credentials server-side

The `app_secret` signs every request — anyone holding it can create orders
on your account. Never embed it in frontend code, mobile apps or public
repositories. See [Authentication](/guides/authentication).

## Sign exactly what you send

If you integrate without the SDK: compute the signature over the **exact**
`request` string you send. Re-serializing the payload after signing
(different key order, whitespace, escaping) is the most common cause of
`Invalid signature` errors — see [Request signing](/legacy/hmac-signing).

## Verify webhook signatures

If you use [push tracking](/guides/tracking#push-tracking-webhooks), always
verify the notification signature before processing it — otherwise anyone
who finds your endpoint URL can inject fake status updates. The SDK's
`PushTrackingWebhook` refuses unverified payloads by design.

## Identify your integration

Send a descriptive `User-Agent` (application name and version) with every
request. When something misbehaves — a burst of failing orders, a retry
storm — Apaczka can then tell which integration and which release is behind
it and reach out before it becomes an incident. The [PHP SDK](/sdk) does this
for you (`AlsendoOneSDK/<version> PHP/<version>`); raw integrations should set
the header themselves.

## Test in the sandbox first

Use the [sandbox environment](/guides/testing) for development and CI —
production orders are real orders with real couriers and real invoices.
