# Amazon Connect to Magento / Adobe Commerce order lookup (Lambda handler)

Companion to the post [Connect + Magento (Oct 2026)](/blog/amazon-connect-magento-integration-2026/).

A Node 24 ESM Lambda with no npm dependencies. It reads an `orderNumber` or `email` flow parameter, or the caller's phone (`CustomerEndpoint.Address`, E.164), calls `GET /rest/{store_code}/V1/orders` with a `searchCriteria` filter sorted by `created_at` descending, and returns a flat map of strings for an **Invoke AWS Lambda function** block set to `STRING_MAP` response validation.

> **Not run against a live store.** The tests mock `fetch`. Nothing here has touched Magento Open Source or Adobe Commerce. The phone path and the OAuth 1.0a signing are the least certain parts; see below.

> **Verify before you speak.** A caller ID or an order number does not prove identity. Check a second factor in a separate flow step before the flow reads any order detail aloud.

| Key | Meaning |
| --- | --- |
| `lookupStatus` | `OK` or `FALLBACK`. Always present. |
| `fallbackReason` | Empty on `OK`. Otherwise `no_match`, `ambiguous`, `auth` (401), `blocked_or_forbidden` (403, for example a WAF block), `throttled` (429), `bad_request` (400), `timeout`, `bad_response` (200 without the expected fields), `not_configured`, `no_identifier`, `phone_unsupported`, `error` |
| `orderRef`, `orderStatus`, `placedOn`, `itemCount`, `isGuest` | Present only on `OK`. No name, email, phone or address. |

Route on `lookupStatus` first and on `fallbackReason` second. `FALLBACK` with `timeout` means "we could not check", which the flow must not say as "no order".

## Identifier order

1. `orderNumber` parameter: filter `increment_id`, page size 1. Finds guest orders.
2. `email` parameter: filter `customer_email`, page size 1. Finds guest orders too.
3. Phone: only if `MAGENTO_PHONE_LOOKUP=1`. It filters orders on `billing_address.telephone`, page size 5, and returns `ambiguous` if the rows belong to more than one buyer. Adobe's search documentation says only top-level attributes can be searched and the core order route documents no such filter, so this is unverified and off by default. If your store rejects it you get `bad_request`.

## Configuration (environment variables)

| Variable | Default | Note |
| --- | --- | --- |
| `MAGENTO_BASE_URL` | none | `https://shop.example.com`, no trailing path |
| `MAGENTO_STORE_CODE` | `default` | The `/rest/{store_code}/V1/...` segment |
| `MAGENTO_AUTH` | `bearer` | `bearer` or `oauth1` |
| `MAGENTO_BEARER_TOKEN` | none | Customer-independent admin token, or an integration token on a store that set `oauth/consumer/enable_integration_as_bearer` to 1. Admin tokens expire (4 hours by default) and need two-factor handling, so prefer `oauth1`. |
| `MAGENTO_CONSUMER_KEY`, `MAGENTO_CONSUMER_SECRET`, `MAGENTO_ACCESS_TOKEN`, `MAGENTO_ACCESS_TOKEN_SECRET` | none | Integration credentials for `oauth1` (HMAC-SHA256) |
| `MAGENTO_PHONE_LOOKUP` | off | `1` enables the unverified phone path |
| `LOOKUP_BUDGET_MS` | `5000` | One `AbortController` covers the request. Clamped to 6000, under the flow block's 8 s maximum |

The artifact reads environment variables for brevity. In Lambda, load the secrets from Secrets Manager (`secretsmanager:GetSecretValue` on one secret).

Lambda function timeout: a little above `LOOKUP_BUDGET_MS` (for example 6 s). Flow block timeout above that (for example 7 s, maximum 8 s).

## Run the test

```bash
node --test
```

Twelve tests: happy path and URL shape, no match, two buyers on one phone, guest order, phone path off by default, status mapping (401, 403, 429, 400, 500), timeout abort, 200 with missing fields or non-JSON, missing config and bad identifier, a log line without the phone, email or order number, and a self-consistency check of the OAuth 1.0a signature. None of them calls Magento, so they prove nothing about your store's field names, filters, WAF or latency.

## Not included

Webhook or event ingestion, a DynamoDB cache, Customer Profiles ingestion, retries (Connect already retries a thrown error, which is why this handler returns a fallback instead), and the IAM policy. Magento side: create a dedicated integration whose role has the Sales order view resource (`Magento_Sales::actions_view`) and nothing else it does not need.
