---
title: Multiple Kassas
description: Serve several Payme, Click, Paynet or Uzum merchant accounts (kassas) from one Django webhook URL with PayTechUZ 0.5 — separate books, sandbox kassas and kassa checks.
source: https://pay-tech.uz/en/docs/integrations/multiple-kassas
---

# Multiple Kassas

A business often holds several merchant accounts with one provider: a live
kassa and a sandbox one, or one per brand. Since **0.5.0** each Django webhook
serves all of them from one URL. Single-kassa settings keep working unchanged.

## How a call finds its kassa

List the kassas under `KASSAS`. The provider's own credentials decide which
one a call belongs to:

| Gateway | A call is matched by |
| --- | --- |
| Payme | the key in its Basic auth header (`PAYME_KEY`) |
| Click | `service_id` in the body, signed with that kassa's `SECRET_KEY` |
| Paynet | `serviceId` in the body, else the login (`USERNAME`) |
| Uzum | the Basic auth login (`USERNAME`); the call must then name that kassa's `serviceId` |

A call that matches no kassa is refused as unauthenticated.

## Settings

```python
PAYTECHUZ = {
    'PAYME': {
        'KASSAS': {
            'shop':  {'PAYME_ID': '...', 'PAYME_KEY': '...'},
            'gifts': {'PAYME_ID': '...', 'PAYME_KEY': '...'},
        },
        'KASSA_FIELD': 'kassa',          # optional, see below
        'ACCOUNT_MODEL': 'shop.models.Order',
        'ACCOUNT_FIELD': 'id',
        'AMOUNT_FIELD': 'amount',
        'ONE_TIME_PAYMENT': True,
    },
    'CLICK': {
        'KASSAS': {
            'shop': {'SERVICE_ID': '...', 'SECRET_KEY': '...'},
        },
        # ACCOUNT_MODEL etc. as above
    },
    'PAYNET': {
        'KASSAS': {
            'live': {'SERVICE_ID': '1001', 'USERNAME': '...', 'PASSWORD': '...'},
            'test': {'SERVICE_ID': '1003', 'USERNAME': '...', 'PASSWORD': '...',
                     'IS_SANDBOX': True},
        },
    },
    'UZUM': {
        'KASSAS': {
            'shop': {'SERVICE_ID': '...', 'USERNAME': '...', 'PASSWORD': '...',
                     'ALLOWED_IPS': ['...']},
        },
        'IP_HEADER': 'HTTP_X_REAL_IP',
        'BEHIND_CLOUDFLARE': True,
    },
}
```

Your view does not change: the same `BasePaymeWebhookView` subclass on the
same URL serves every kassa. Inside a hook, `self.kassa` is the name of the
kassa that called.

## Separate books

Every row in the `payments` table carries its `kassa`, and every lookup is
scoped to the kassa that called. One kassa never sees, performs, cancels or
reports another's transactions, and the same provider transaction id may exist
once per kassa. Rows from before 0.5 have `kassa = ''`, which is what a
single-kassa setup uses.

## Which kassa may take an order

Set `KASSA_FIELD` to a field on your account model that holds the kassa the
account must be paid through. A call from any other kassa is told the account
does not exist. A Paynet kassa may set `ACCOUNT_KASSA` when the value stored
on accounts differs from its key in `KASSAS` — useful for a sandbox and a live
service that share accounts.

Without `KASSA_FIELD`, any kassa may pay any account.

## Sandbox kassas

Paynet and Uzum kassas take `IS_SANDBOX: True`. Payments are recorded, but
`sandbox_payment(params, transaction)` is called instead of
`successfully_payment`, so nothing is delivered. The account stays payable for
the provider's tester, and sandbox rows stay out of live statements.

## Moving an existing setup to `KASSAS`

1. Upgrade and migrate: `pip install "paytechuz[django]>=0.5"`, then
   `python manage.py migrate`.
2. Existing rows have `kassa = ''`. Before switching to `KASSAS`, set each
   row's `kassa` to the name it will have, otherwise the new kassa cannot see
   them (for refunds, checks and statements).
3. Switch the settings and deploy.

Rolling back to 0.4 on Django 5+ needs no reverse migration: the column has a
database default and the old code ignores it.
