> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paymove.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> ## paymove integration rules for AI agents
>
> These rules are verified against the live API. Follow them exactly.
>
> 1. Amounts are INTEGERS in grosze (minor units). `1000` means 10.00 PLN. PLN is the only supported currency and there is no currency field in the API.
> 2. A float `price` (e.g. `12.99`) is SILENTLY TRUNCATED to 12 grosze and still returns HTTP 200. Always send an integer: `Math.round(pln * 100)`.
> 3. Authenticate with the `X-API-KEY` header. Never `Authorization: Bearer`. Sandbox keys start with `sk_test_`, production keys with `sk_live_`.
> 4. Server-side only. A call from a merchant page is rejected. Never put the key in frontend code.
> 5. Unknown request fields are silently ignored and still return HTTP 200 - a wrong body shape looks like success. Match the documented shape exactly.
> 6. A missing `externalId` returns HTTP 500 `Something went wrong`, not 400. A missing `price` or `details.returnUrl` returns HTTP 200 and a usable `redirectUrl` - no error at all. Validate the body yourself before sending it.
> 7. Always verify the `X-Paymove-Signature` header on incoming webhooks before trusting them: https://docs.paymove.io/en/webhook-signature.md
> 8. Errors are `{"status": <int>, "message": "<text>"}`. Branch on the HTTP status only - never on `message`, which is unstable and leaks internal class names.
> 9. There is no rate limiting, no HTTP 429, no HTTP 422, no `Idempotency-Key` header and no API versioning. Do not write code that handles them.
> 10. Re-POSTing an `externalId` that already exists returns HTTP 200 with the ORIGINAL `redirectUrl` and silently discards EVERY field you send - the new price, description and details are all ignored. Use `PATCH /api/pay/product/{productId}/subproduct/{externalId}` to change a price.
> 11. Webhooks fire only on `COMPLETED` by default. Registering a webhook requires both `retries` and `expectedCode` - omitting either returns HTTP 500, and `retries: 0` means no retry at all. Delivery counts as successful when the HTTP status equals `expectedCode` - the response body is never inspected.
> 12. Never fulfil an order on the `returnUrl` redirect. The checkout does not redirect there by itself: the customer has to click "back to shop", and inside the widget modal that redirect never happens. Fulfil only in the webhook handler, after verifying the signature.
> 13. Do not pin a version of `@paymove-io/sdk` - install the latest.
> 14. If the merchant has no `productId` yet, do not stop at a placeholder: ask for the API key, the `partnerId` and the shop name, propose defaults for `location` and `timezone` (`Europe/Warsaw`), offer the optional fields once, then create the product for them with `POST /api/product/pay` and take `productId` from `id` in the response. Do the same for the webhook: register it, then assign it to the product. Guided procedure: https://docs.paymove.io/skill.md (Steps 1-2).
> 15. Full documentation index: https://docs.paymove.io/llms.txt · Copy-paste quickstart: https://docs.paymove.io/en/quickstart.md · Agent skill: https://docs.paymove.io/skill.md

# Apple Pay activation

> Apple Pay in the payment button: your page's domain in the product's metadata.merchantDomain and the Apple verification file prepared by paymove.

In the modal, the paymove payment page runs in an iframe embedded on **your** page. Apple lets the Apple Pay sheet start only on a domain that is registered and verified with Apple. By default only paymove domains are registered, so Apple Pay in the widget requires a one-time onboarding of your domain.

BLIK and Google Pay work in the widget right away - this page is about Apple Pay only.

<Steps>
  <Step title="Set the domain in the product">
    Set `metadata.merchantDomain` of the product you embed in the widget.
  </Step>

  <Step title="Write to us">
    Send the domain to [integration@paymove.io](mailto:integration@paymove.io). We will register it with Apple and send you a verification file.
  </Step>

  <Step title="Publish the verification file">
    Serve the file at a fixed URL on your domain.
  </Step>

  <Step title="Wait for confirmation">
    Once Apple verifies the domain, we will confirm the registration - from then on Apple Pay works in the modal.
  </Step>
</Steps>

## 1. `merchantDomain` in the product

`merchantDomain` is the domain of the page where you paste the widget script - the one the customer sees in the address bar. **Not** the paymove domain from the script URL.

The most convenient way is to set it when creating the product:

```
POST /api/product/pay
```

```json theme={"theme":{"light":"one-dark-pro","dark":"one-dark-pro"}}
{
  "partnerId": "78562c79-2f5c-4415-8af4-c871eea92ef2",
  "productType": "PAY",
  "name": "Example Foundation",
  "location": "Warszawa",
  "timezone": "Europe/Warsaw",
  "metadata": {
    "locale": "pl-PL",
    "merchantDomain": "foundation.example.com"
  }
}
```

You add it to an existing product with an update. Mind two differences compared to creating a product:

```
PATCH /api/product/pay/{productId}
```

```json theme={"theme":{"light":"one-dark-pro","dark":"one-dark-pro"}}
{
  "productMetadata": {
    "locale": "pl-PL",
    "logoUrl": "https://foundation.example.com/logo.png",
    "merchantDomain": "foundation.example.com"
  }
}
```

<Warning>
  * In the update the field is called **`productMetadata`**, not `metadata`. A body with `metadata` returns `200` but changes nothing - unknown fields are ignored.
  * `productMetadata` **replaces all metadata** of the product. Include every field you have already set (`locale`, `logoUrl` and the rest) - omitted ones will be cleared.
</Warning>

<Info>
  If you don't manage the product through the API, include the domain in your step-2 message - we'll set `merchantDomain` on our side.
</Info>

Rules:

* **Domain only** - without `https://`, port, path or trailing slash: `foundation.example.com`. If you send a full URL, the API keeps only the domain and converts it to lowercase. A value from which no valid domain can be extracted results in `400`.
* **Exactly the domain** the page runs on. `example.com` and `www.example.com` are two different domains for Apple - use the one under which the customer actually sees the page (and ideally redirect the other one to it).
* **One domain per product.** If the same product should work in the widget on several domains, write to us - each domain has to be registered with Apple separately.
* `localhost` and domains without public HTTPS won't pass Apple's verification. Test Apple Pay on a domain reachable from the internet.

## 2. Domain registration request

Write to [integration@paymove.io](mailto:integration@paymove.io) and provide:

* the domain (or domains) where you embed the widget,
* the product IDs (`productId`) that will run there,
* the environment - sandbox, production or both.

We will register the domain with Apple for paymove's merchant identifier and send you a **verification file** generated for your domain.

<Info>
  You can't generate the file yourself in your Apple Developer account - it is tied to the Apple merchant identifier owned by paymove. You don't need your own Apple Developer account.
</Info>

## 3. Verification file

Serve the file you received at exactly this URL:

```
https://foundation.example.com/.well-known/apple-developer-merchantid-domain-association
```

Apple checks it automatically, so it must be:

* available **publicly over HTTPS**, without login, geo-blocking or bot protection (Cloudflare challenge, WAF),
* at **exactly this path** - the `.well-known` directory in the domain root, file name **without an extension**,
* served **without a redirect** (status `200`, not `301`/`302`),
* **unmodified** - don't edit it or change its encoding.

Check it before reporting that it's ready:

```bash theme={"theme":{"light":"one-dark-pro","dark":"one-dark-pro"}}
curl -i https://foundation.example.com/.well-known/apple-developer-merchantid-domain-association
```

The expected result is `HTTP/2 200` and the file contents from the first byte. Example configurations:

<CodeGroup>
  ```nginx nginx theme={"theme":{"light":"one-dark-pro","dark":"one-dark-pro"}}
  location = /.well-known/apple-developer-merchantid-domain-association {
    alias /var/www/apple/apple-developer-merchantid-domain-association;
    default_type text/plain;
  }
  ```

  ```text Next.js / Vite / static hosting theme={"theme":{"light":"one-dark-pro","dark":"one-dark-pro"}}
  public/.well-known/apple-developer-merchantid-domain-association
  ```

  ```text WordPress theme={"theme":{"light":"one-dark-pro","dark":"one-dark-pro"}}
  Upload the file via FTP/SFTP to the installation root directory:
  /.well-known/apple-developer-merchantid-domain-association
  (no URL-rewriting plugins - the file must be served directly)
  ```
</CodeGroup>

<Warning>
  Don't remove the file after verification. Apple may check the domain again, and a missing file will disable Apple Pay in the widget.
</Warning>

## 4. Confirmation and testing

Once the file is in place, let us know - we will ask Apple for verification and confirm the result. Then test a payment:

* in **Safari on an iPhone, iPad or Mac** with a card added to Apple Wallet - Apple Pay does not show in other browsers,
* on a page **under the registered domain** - a test version under another domain (e.g. `staging.example.com`) needs a separate registration.

## What happens without registration

Until the domain is registered, Apple rejects the payment session: the customer chooses Apple Pay, but the payment sheet doesn't open or closes with an error. Other methods work normally. If you don't plan to register, disable Apple Pay for the product embedded in the widget - write to us and we will do it for you.

Registration is not needed when the customer pays **outside the widget** - on the payment page opened from a link or a QR code, the Apple Pay sheet shows on the paymove domain.

<Info>
  The same procedure applies to the [checkout widget](/en/sdk/widget#apple-pay-in-the-modal). If you embed both widgets on one domain, report it in a single message - list all products that should work there.
</Info>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.