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

# Webhooks

> Outgoing Discord notifications and payment-provider webhooks handled by the platform.

ItemShop has two kinds of webhooks:

* **outgoing** — the shop posts messages to your Discord channel (new order, daily report, announcement);
* **incoming from payment providers** (IPN) — the provider confirms a payment, and the platform verifies the signature and updates the order status. You don't implement this yourself — a correctly configured payment method is enough.

> [!NOTE]
>
> ItemShop doesn't currently send generic, signed HTTP webhooks to arbitrary URLs. To react to orders in your own system, poll `GET /orders/<shopId>?status=paid` with an `orders:read` key (see [API with a key](https://andcze.mintlify.site/en/developers/api-keys.md)), and use the [plugin protocol](https://andcze.mintlify.site/en/developers/plugin-protocol.md) to deliver items in game.

## Discord — setup

**1. Create a webhook in Discord**

Discord server settings → **Integrations → Webhooks** → new webhook on the chosen channel → copy its URL.

**2. Paste the URL in the panel**

**Integrations → Discord** → the **Discord webhook URL** field in the **Connection** section. The URL must start with `https://discord.com/api/webhooks/` (`discordapp.com`, `canary.discord.com` and `ptb.discord.com` are also allowed). Other URLs are rejected — this protects against SSRF.

**3. Enable message types and save**

The **"Send a notification for new orders"** and **"Send daily sales reports"** toggles are independent. One **Save integration** button saves both tabs.

Through the API you set the same thing with `PUT /shops/<shopId>` (requires `shop:write`): the fields `discordWebhookUrl`, `discordWebhook` (order-notification look) and `discordWebhookDaily` (daily-report look). API responses never return the full URL — you get `hasDiscordWebhook` and a masked tail `discordWebhookUrlMasked` instead.

> [!WARNING]
>
> A Discord webhook URL works like a password: anyone who knows it can post to your channel. Discord messages aren't signed — the URL's secrecy is the only protection. If it leaks, delete the webhook in Discord and paste a new one.

## When messages are sent

| Event | When | Toggle |
| - | - | - |
| New order | after the order is paid (status `paid`) | "Send a notification for new orders" |
| Daily report | every day at 8:00 Polish time, for the previous day | "Send daily sales reports" |
| Announcement | when you approve an announcement drafted by the AI assistant | — |

Voucher redemptions (free orders) are not counted in report amounts.

## Look and placeholders

Each message type has its own title (up to 120 chars), bar colour, sender name (up to 80 chars), avatar URL and a message above the embed (up to 1500 chars). Fields come with sensible defaults; the avatar is optional.

In the message above an order embed you can use:

| Placeholder | Inserts |
| - | - |
| `{nick}` | the player's nickname |
| `{product}` | the product name |
| `{amount}` | the order amount |
| `{server}` | the server |
| `{orderId}` | the order number |
| `{date}` | the order date |
| `{paymentMethod}` | the payment method |
| `{quantity}` | the number of items |

In a daily report:

| Placeholder | Inserts |
| - | - |
| `{date}` | the day the report covers |
| `{revenue}` | total revenue |
| `{net}` | net revenue (after costs) |
| `{orders}` | number of orders |
| `{customers}` | number of customers |

Placeholders from the two tables aren't interchangeable. An unknown placeholder is left in the text unchanged.

## Example payload

This is the request ItemShop sends to Discord after a new order (with the message `Player {nick} bought {product} for {amount}!`). Default field labels are in Polish; you can rename them in the embed editor.

```json theme={null}
{
  "content": "Player Steve bought VIP 30 days for 19.99 PLN!",
  "username": "ItemShop",
  "embeds": [
    {
      "title": "🛒 Nowe zamówienie",
      "color": 65280,
      "fields": [
        { "name": "Gracz:", "value": "Steve", "inline": true },
        { "name": "Produkt:", "value": "VIP 30 days", "inline": true },
        { "name": "Ilość:", "value": "1", "inline": true },
        { "name": "Kwota:", "value": "19.99 PLN", "inline": true },
        { "name": "Serwer:", "value": "Survival", "inline": true },
        { "name": "Metoda płatności:", "value": "BLIK", "inline": true }
      ],
      "timestamp": "2026-10-27T10:30:00.000Z",
      "footer": { "text": "🛍️ Sklep: MyServer | Czas Polski" }
    }
  ]
}
```

## Delivery and retries

* Each message is **a single** `POST` attempt to Discord — there is no retry queue.
* A failed send (e.g. deleted webhook, Discord rate limit) is logged and **doesn't block** the order or its fulfillment.
* An empty webhook URL means no messages, regardless of the toggles.

If nothing arrives: check the URL, the right toggle and that **Save integration** was clicked. The easiest test is a free order (100% discount) or redeeming a voucher.

## Payment-provider webhooks

The payment provider sends its notification to a public platform endpoint:

```http theme={null}
POST /api/v2/payments/webhooks/<provider>/<shopId>/<webhookSecret>
```

* `provider` — the provider identifier (e.g. `imojeTransfer`, `zen`, `paymentic`);
* `shopId` — the shop the payment method belongs to;
* `webhookSecret` — the payment method's secret, set during configuration.

The URL is passed to the provider automatically when the payment session is created, or you configure it on the provider's side.

### Verification

The platform checks the secret in the URL and the provider's signature over the **original, unmodified** request body before processing anything:

| Provider | Signature |
| - | - |
| imoje | `X-Imoje-Signature` header |
| ZEN | IPN hash in the notification body |
| Paymentic | `X-Paymentic-Signature` header |
| Stripe | `Stripe-Signature` header |

A forged webhook, wrong secret, mismatched provider or different shop → **`401`**. Don't modify notification bodies in a proxy — an altered body fails verification.

### Statuses and response

| Provider status (examples) | Order status |
| - | - |
| `settled`, `ACCEPTED`, `PAID` | `paid` — ready for fulfillment |
| `cancelled`, `rejected`, `FAILED`, `EXPIRED` | `cancelled` |
| `pending`, `CREATED` | unchanged |

After processing, the endpoint returns `200` with `{ "received": true }`; providers treat `2xx` as acknowledgement. Providers retry and send duplicates — the transition to `paid` is idempotent, so an order is marked paid (and the email and Discord notification are sent) exactly once.

## Next

> **[Payments](https://andcze.mintlify.site/en/shop/payments.md)**
>
> Configure providers and payment methods.

> **[Plugin protocol](https://andcze.mintlify.site/en/developers/plugin-protocol.md)**
>
> How a paid order reaches the game server.

> **[API with a key](https://andcze.mintlify.site/en/developers/api-keys.md)**
>
> Polling orders from your own system.

> **[Errors and limits](https://andcze.mintlify.site/en/developers/errors-and-limits.md)**
>
> `401`, `409`, `429` and retries.


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