> ## 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, and `retries` defaults to `0` (no retries) unless you set it explicitly. 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. 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

# Configuration

> One-time payment gateway setup: create a product, register a webhook and assign it to the product.

Integrating with the Paymove payment gateway consists of four steps: you create a product, register a webhook, assign it to the product, and then create payments via the SDK or REST API.

***

## 1. Create a product

A product represents your store in the Paymove system. All payments and subproducts are created within this product.

**Example product creation request:**

```
POST https://gateway-api.sandbox.paymove.io/api/product/pay
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "partnerId": "{{partnerId}}",
  "productType": "PAY",
  "id": "2f6c19e8-84a7-4f50-b950-8d5a05e0bbf2",
  "name": "MerchantShop",
  "fullName": "Merchant Shop Sp. z o.o.",
  "shortName": "MShop",
  "location": "PL",
  "timezone": "Europe/Warsaw",
  "productMetadata": {
    "locale": "pl-PL"
  }
}
```

| Field                    | Type     | Description                                          |
| ------------------------ | -------- | ---------------------------------------------------- |
| `partnerId`              | `uuid`   | Partner identifier (internal, assigned by Paymove)   |
| `productType`            | `enum`   | Product type -  `PAY` for payment gateway            |
| `id`                     | `uuid`   | Product UUID                                         |
| `name`                   | `string` | Short product name                                   |
| `fullName`               | `string` | Full company name                                    |
| `shortName`              | `string` | Display name                                         |
| `location`               | `string` | Location (country code)                              |
| `timezone`               | `string` | IANA timezone (e.g. `Europe/Warsaw`)                 |
| `productMetadata.locale` | `string` | Optional -  default checkout language (e.g. `pl-PL`) |

***

## 2. Register a webhook

A webhook is a URL on your side that Paymove calls whenever a payment status changes. This way you don't need to manually check the status - orders can be fulfilled automatically. For the list of possible statuses, see the [status table](#how-the-webhook-works).

**Example webhook registration request:**

```
POST https://gateway-api.sandbox.paymove.io/api/pay/plugin/webhook
```

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "name": "PaymentSuccessHook",
  "endpoint": "https://merchant-shop.com/api/payments/webhook",
  "method": "POST",
  "requestTemplate": {
    "orderId": "{{externalId}}",
    "price": "{{price}}"
  },
  "responseTemplate": {
    "status": "ok"
  },
  "expectedCode": 200,
  "expectedResponse": "{ \"status\": \"ok\" }",
  "retries": 3,
  "partnerId": "{{partnerId}}",
  "type": "PAYMENT",
  "headers": {
    "Content-Type": ["application/json"]
  }
}
```

| Field              | Type      | Description                                                                                                                                                      |
| ------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`             | `string`  | Webhook name                                                                                                                                                     |
| `endpoint`         | `string`  | URL where Paymove sends a notification after a successful payment                                                                                                |
| `method`           | `enum`    | HTTP method (`POST`)                                                                                                                                             |
| `requestTemplate`  | `object`  | Payload template -  every field of the default payload is available as a variable, including `{{externalId}}`, `{{price}}`, `{{status}}` and `{{paymentMethod}}` |
| `responseTemplate` | `object`  | Expected response structure from the merchant                                                                                                                    |
| `expectedCode`     | `integer` | Expected HTTP response code (`200`). **The field is required** - omitting it results in a `500` error                                                            |
| `expectedResponse` | `string`  | Expected response as a JSON string. Stored on the webhook but **never compared** against the actual response                                                     |
| `retries`          | `integer` | Number of retry attempts on failure. **The field is required** - omitting it results in a `500` error. A value of `0` means no retries                           |
| `partnerId`        | `uuid`    | Partner identifier (internal)                                                                                                                                    |
| `type`             | `enum`    | Event type (`PAYMENT`)                                                                                                                                           |
| `headers`          | `object`  | HTTP headers attached to the webhook                                                                                                                             |

### How the webhook works

<Warning>
  **By default the webhook is sent only for the `COMPLETED` status.** Notifications for the remaining statuses (`CANCELED`, `ERROR`, `REFUNDED`) must be enabled by Paymove for your specific product - write to [integration@paymove.io](mailto:integration@paymove.io).
</Warning>

Payment statuses in Paymove:

| Status                        | Meaning                                                 |
| ----------------------------- | ------------------------------------------------------- |
| `INITIALIZED`                 | Payment created, the customer has not paid yet          |
| `PENDING`                     | Payment in progress on the provider's side              |
| `COMPLETED`                   | Payment succeeded - **fulfil the order**                |
| `CANCELED`                    | The customer cancelled the payment                      |
| `ERROR`                       | The payment failed                                      |
| `REFUNDED`                    | The payment was refunded                                |
| `WAITING_FOR_EXTERNAL_ACTION` | Transient internal Paymove state - not a terminal state |

<Info>
  The status is always sent as a **string** (e.g. `"COMPLETED"`). The numbers found in older integrations are an internal database representation and never appear in the API or in webhooks.
</Info>

### Webhook payload

The payload shape depends on whether you set a `requestTemplate`.

**Without a `requestTemplate`** Paymove sends the full object (empty fields are omitted):

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "id": "891412c8-8717-4449-9543-e34112bec470",
  "name": "MerchantShop",
  "fullName": "Merchant Shop Sp. z o.o.",
  "shortName": "MShop",
  "location": "PL",
  "externalId": "order-123",
  "orderId": "PAY1784798914400",
  "price": 950,
  "status": "COMPLETED",
  "paymentMethod": "BLIK",
  "email": "customer@example.com",
  "date": 1783246791.745352526,
  "requestId": "8f2b1c44-0d7e-4a91-b2c3-5e7f9a1d3c60"
}
```

<Warning>
  In the default payload, `price` is the amount **minus the Paymove fee**, not the amount charged to the customer. If you verify the amount, compare it against the value you stored when creating the payment, not against this field.
</Warning>

**With a `requestTemplate`** the payload is exactly what your template renders. For the template from step 2:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{
  "orderId": "order-123",
  "price": "1000"
}
```

| Field     | Type     | Description                                            |
| --------- | -------- | ------------------------------------------------------ |
| `orderId` | `string` | The `{{externalId}}` value - your order identifier     |
| `price`   | `string` | The `{{price}}` value - amount in grosze (as a string) |

### Signature verification

Every webhook request carries an `X-Paymove-Signature` header. **Verify it before trusting the payload** - without verification, anyone who knows your URL can impersonate Paymove and have an order fulfilled without paying.

Ready-to-copy code for Node.js, Python and Java: [Webhook signature verification](/en/webhook-signature).

### Required response

Your server must respond with an HTTP status equal to `expectedCode`. The customary body to return alongside it is:

```json theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
{ "status": "ok" }
```

<Warning>
  Delivery success is decided by the **HTTP status alone**, and it must be **exactly equal** to `expectedCode`. With the default `200`, a `201` or `204` response counts as a failure - and worse, it is **not retried**: only 4xx, 5xx and network errors trigger the retry schedule. A wrong 2xx loses the notification for good.
</Warning>

<Info>
  The response body is **never inspected**. `expectedResponse` is stored on the webhook and echoed back by the API, but it is never compared against what your server returns - any body will do.
</Info>

If delivery fails, the webhook is retried as many times as `retries` specifies - **`0` by default, meaning not at all**. With `retries` set, the next attempts follow after 1 s, 5 s, 5 min, 1 h, and then every 3 h.

<Info>
  Paymove applies no timeout to webhook calls. Respond immediately and do the actual order processing asynchronously - otherwise a slow endpoint blocks processing.
</Info>

***

## 3. Assign webhook to product

After creating a product and registering a webhook, you need to link them together. This way every payment event related to this product is automatically sent to the specified webhook URL.

**Example request:**

```
POST https://gateway-api.sandbox.paymove.io/api/pay/plugin/webhook/{webhookId}/products/{productId}
```

| Parameter   | Type   | Description                            |
| ----------- | ------ | -------------------------------------- |
| `webhookId` | `uuid` | Webhook identifier received in point 2 |
| `productId` | `uuid` | Product UUID created in point 1        |

***

## 4. Creating payments

After configuring the product and webhook, you can start creating payments. Choose your integration method:

<CardGroup cols={2}>
  <Card title="SDK" icon="npm" href="/en/sdk/javascript">
    Node.js package -  initialize the client and call `createPayment`.
  </Card>

  <Card title="REST API" icon="code" href="/en/rest-api">
    Direct HTTP calls -  works with any language.
  </Card>
</CardGroup>
