# Run a B2B payment contract end to end Building with an AI agent? Start at https://docs.payme.io/agents.md ## Prerequisites - The initiator's PayMe seller account and its seller_payme_id (MPL) - For card or direct-debit contracts: the payer's payment method attached to its seller account ## Related - [Contracts](/b2b-payments/contracts) - [Create a payment contract](/b2b-payments/contracts/post-user) - [List payment contracts](/b2b-payments/contracts/get-contracts) - [List previous recipients](/b2b-payments/contracts/get-recipient) - [Upload contract files](/b2b-payments/contracts/post-contracts-files) - [List contract files](/b2b-payments/contracts/get-contracts-files) - [Save a card (token sale)](/payments/tokenization/post-generate-sale-tokenization) - [Attach a saved card to a seller](/payments/merchants-management/post-add-card-to-seller) - [List sellers](/payments/query-apis/post-get-sellers) - [Callbacks](/payments/getting-started/callbacks) - [Callback types reference](/payments/getting-started/callback-types) - [Transfer funds between sellers](/b2b-payments/wallets/post-generate-transfer) A payment contract moves money between two PayMe sellers on a date you set, after the other seller approves it. This guide takes one contract from start to finish: choose the direction, prepare the payment method, create the contract, follow its approval with callbacks, change or cancel it, attach invoices, and handle errors. The concepts are on [B2B payment contracts](/b2b-payments/contracts/overview), and each step links to the reference page with the full field tables. Every call in this guide: - goes to `https://sandbox.payme.io/api` while you test, and to `https://live.payme.io/api` in production; - authenticates with the seller's `seller_payme_id` (MPL) in the `PayMe-Merchant-Key` header. A partner acting for one of its sellers sends that seller's MPL, and can add its own client key in the `PayMe-Partner-Key` header; - sends JSON (`Content-Type: application/json`), with amounts in minor units (`500000` = 5,000.00). Keep the MPL on your server. See [Hosts and authentication](/b2b-payments/contracts/overview#hosts-and-authentication). ## Step 1: Choose the direction and the payment method The seller that creates the contract is the **initiator**. The other seller is the **recipient**, and the recipient always approves the contract. `payment.type` sets who pays: | `payment.type` | Use it to | Who pays | Who approves | |---|---|---|---| | `1` – send | Pay another seller, for example a supplier | The initiator | The recipient | | `2` – request | Collect a payment from another seller | The recipient | The recipient | `payment.method` sets where the money comes from: | `payment.method` | Paid from | Limits | |---|---|---| | `funds-transfer` | The payer's PayMe balance | Don't send `buyer_key` (error `394`) | | `credit-card` | A card attached to the payer's seller account | Up to `5000000` (50,000.00) per contract (error `393`) | | `il-direct-debit` | An Israeli direct-debit authorization attached to the payer's seller account | `ILS` only (error `320`). Up to `5000000` per contract (error `393`) | The currency must be enabled for both sellers (error `252`). The payment requests in this guide use `funds-transfer`. A request paid by card or direct debit depends on a payment method that the recipient sets up for your contracts in PayMe: contact PayMe before you build one. ## Step 2: Prepare the payer's card or direct debit Do this step only for a contract that sends a payment (`payment.type` `1`) by `credit-card` or `il-direct-debit`. With `funds-transfer`, go to step 3. **A card:** 1. Tokenize the card with [`generate-sale`](/payments/tokenization/post-generate-sale-tokenization) and `sale_type` `token`. The `sale-complete` callback carries the card's `buyer_key`. 2. Attach the `buyer_key` to the initiator's seller account with [`add-card-to-seller`](/payments/merchants-management/post-add-card-to-seller). 3. Check that the card appears in `seller_credit_cards` of [`get-sellers`](/payments/query-apis/post-get-sellers). It must still be valid on the contract's `payment.due_date` (error `390`). **A direct-debit authorization:** tokenize the bank account with `"sale_payment_method": "il-direct-debit"`. You don't attach it yourself: once the bank approves it, PayMe attaches it to the seller account, and it appears in `seller_bank_authorizations` of `get-sellers` with `auth_status` `Active`. Until then it can't pay a contract (error `391`). The requests, with examples, are on [Add a card or direct-debit authorization](/b2b-payments/contracts/overview#add-a-card-or-direct-debit-authorization). ## Step 3: Create the contract Call [`POST /payment-contracts`](/b2b-payments/contracts/post-user). This contract sends 5,000.00 by card on December 22: ```bash curl -X POST https://sandbox.payme.io/api/payment-contracts \ -H 'Content-Type: application/json' \ -H 'PayMe-Merchant-Key: MPLDEMO-MPLDEMO-MPLDEMO-1234567' \ -d '{ "description": "December payment", "amount": 500000, "currency": "ILS", "buyer_key": "BUYERDEMO-BUYERDEMO-BUYERDEMO-1234567", "remote_id": "INV-2026-12", "callback_url": "https://your-server.example.com/payme/contracts", "recipient": { "email": "supplier@example.com", "phone": "0500000001", "name": "Example Supplies Ltd", "language": "en" }, "payment": { "type": 1, "method": "credit-card", "due_date": "2026-12-22 10:00:00" } }' ``` The call returns HTTP `201` and the contract in status `1` (abridged): ```json { "status_code": 0, "guid": "CONT1700-000000AA-EXAMPLE1-CONT0001", "status": 1, "amount": 500000, "currency": "ILS", "remote_id": "INV-2026-12", "error_code": null } ``` Store `guid` with your order: every later call uses it. **Send `buyer_key` at the top level** of the body, as above. A `buyer_key` inside `payment` is ignored, and PayMe then picks the card itself: among the attached cards that are valid on `payment.due_date`, the one that expires last. For `il-direct-debit`, PayMe picks the last `Active` authorization. See [Choose the payment method of a contract](/b2b-payments/contracts/overview#choose-the-payment-method-of-a-contract). **Name the recipient.** - To pay one specific PayMe seller, send its MPL as `recipient.payme_id`, and no other `recipient` field. For a recipient of your earlier contracts, take its `seller_payme_id_checksum` from [List previous recipients](/b2b-payments/contracts/get-recipient). - Otherwise send `email` or `phone`, or both. PayMe uses them to find the recipient's PayMe account and to send the approval request. A recipient without a PayMe account opens one while approving. These details can match more than one PayMe account, so prefer `payme_id` when you know it. - `business_code` alone must match a PayMe seller (error `381`). **Set the due date** in the future, as `YYYY-MM-DD HH:MM:SS`. This guide follows a one-time contract. To repeat the payment, add `payment.recurring`: see [Create a payment contract](/b2b-payments/contracts/post-user). **Retrying a create is safe for 5 minutes.** A request that repeats the initiator, recipient, `amount`, `currency`, `payment.due_date` and `payment.type` of a contract created in the last 5 minutes, and not failed, returns that contract with the same `guid`. It doesn't create a second contract or send a second `payment-contract-created` callback. After 5 minutes, the same request creates a new contract. When the recipient has no PayMe account yet, PayMe can't tell two such recipients apart. A second contract to another new recipient, with the same amount, currency, due date and type within 5 minutes, returns the first contract. Compare the `recipient` in the response with the one you sent. To request a payment instead, send `payment.type` `2`. For example, to collect 1,200.00 from another seller's PayMe balance: ```json { "description": "Invoice 1002", "amount": 120000, "currency": "ILS", "remote_id": "INV-1002", "callback_url": "https://your-server.example.com/payme/contracts", "recipient": { "payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-7654321" }, "payment": { "type": 2, "method": "funds-transfer", "due_date": "2026-12-31 10:00:00" } } ``` ## Step 4: Follow the approval and the payment After you create the contract, PayMe asks the recipient to approve it, and the recipient approves in PayMe. Each change of status sends a callback to the contract's `callback_url`: | What happens | `status` | `error_code` | `notify_type` | |---|---|---|---| | You created the contract | `1` | | `payment-contract-created` | | The recipient approves | `2`, then `3` | | `payment-contract-recipient-approved`, then `payment-contract-initiator-approved` | | The recipient rejects | `5` | `11000` | `payment-contract-recipient-rejected` | | Nobody approved in time | `5` | `11002` or `11003` | `payment-contract-confirmation-timeout` or `payment-contract-payment-failed` | | You cancel the contract (step 6) | `5` | `11001` | `payment-contract-canceled` | | The due date passed and the payment ran | `4` | | `payment-contract-payment-completed` | | The payment failed | `5` | `11004` | `payment-contract-payment-failed` | - **Approval.** Status `2` doesn't last: when the recipient approves, PayMe confirms the initiator side at once, so the contract moves on to `3` and waits for the due date. You receive both callbacks. - **Payment.** PayMe runs an approved contract once `payment.due_date` has passed. A completed contract is final. - **Failure.** Status `5` always carries `error_code` and `error_text`. Read `error_code` to know why a contract failed, not `notify_type`: a contract that wasn't approved in time can arrive as `payment-contract-payment-failed` too. On `11004`, `error_note` holds the payment error as free text. The codes are on [Errors](/b2b-payments/contracts/overview#errors). - **No approval.** A contract that isn't approved in time is cancelled automatically. Don't rely on a fixed approval window in your own flow: act on the callback. - [B2B payment contracts](/b2b-payments/contracts/overview#callbacks) also lists `payment-contract-initiator-rejected`. Handle it like any status `5` callback: read `error_code`. ### Handle the callbacks Each callback is a `POST` with a form-encoded body (`application/x-www-form-urlencoded`, not JSON). It holds `notify_type` and every field of the contract, the same fields as the create response. - Reply `200` within 5 seconds, then do the slow work. Delivery, retries and ordering work as for sale callbacks: see [Callbacks](/payments/getting-started/callbacks#delivery-retries-duplicates-and-ordering). - Callbacks can arrive more than once and out of order. Act on `status`, and don't let a late callback overwrite a status `4` or `5` you already recorded. - Partners verify `payme_signature` with the contract formula on [B2B payment contracts](/b2b-payments/contracts/overview#callbacks). A seller without a partner client key re-reads the contract instead (below). - Only the initiator's `callback_url` receives callbacks. The recipient side follows its contracts with [List payment contracts](/b2b-payments/contracts/get-contracts) and `recipient=true`. ### Read a contract Read one contract with `GET /payment-contracts/{guid}`. Either seller on the contract can call it: ```bash curl https://sandbox.payme.io/api/payment-contracts/CONT1700-000000AA-EXAMPLE1-CONT0001 \ -H 'PayMe-Merchant-Key: MPLDEMO-MPLDEMO-MPLDEMO-1234567' ``` It returns the contract fields at the top level and again under `data`, plus `status_code`. A seller that isn't on the contract gets error `380`. To reconcile many contracts, page through [List payment contracts](/b2b-payments/contracts/get-contracts), filtered by `status`, `remote_id` or the other filters on that page. ## Step 5: Change a contract Call `PUT /payment-contracts/{guid}` with the fields to change. Either seller on the contract can call it; another seller gets error `385`. Change a contract before its due date. You can change: - `description`; - the card or direct-debit authorization that pays: send its `buyer_key` together with `payment.method`. The same rules as on create apply (errors `389` to `394`). For example, to pay with another attached card: ```bash curl -X PUT https://sandbox.payme.io/api/payment-contracts/CONT1700-000000AA-EXAMPLE1-CONT0001 \ -H 'Content-Type: application/json' \ -H 'PayMe-Merchant-Key: MPLDEMO-MPLDEMO-MPLDEMO-1234567' \ -d '{ "buyer_key": "BUYERDEMO-BUYERDEMO-BUYERDEMO-7654321", "payment": { "method": "credit-card" } }' ``` The response is the updated contract, with `status_code` `0`. You can't change `amount`, `currency`, `payment.type`, `payment.due_date`, `payment.recurring`, `remote_id`, `callback_url`, or the recipient's `payme_id`, `business_code`, `email`, `phone` or `language`: the call fails with HTTP `422` and error `20`. To change any of them, cancel the contract and create a new one. ## Step 6: Cancel a contract Only the initiator can cancel. Call `PUT /payment-contracts/{guid}/cancellations` with a JSON body. `error_note` is your reason, and PayMe stores it on the contract: ```bash curl -X PUT https://sandbox.payme.io/api/payment-contracts/CONT1700-000000AA-EXAMPLE1-CONT0001/cancellations \ -H 'Content-Type: application/json' \ -H 'PayMe-Merchant-Key: MPLDEMO-MPLDEMO-MPLDEMO-1234567' \ -d '{"error_note": "Order cancelled"}' ``` - The contract moves to status `5` with `error_code` `11001` (`Initiator Canceled`), and PayMe sends `payment-contract-canceled`. The response is the contract, at the top level and under `data`. - Send a body: a request without JSON fails with error `900`, and an empty one with `901`. - A completed or failed contract can't be cancelled (error `382`). - The recipient can't cancel through the API. Before approving, the recipient can reject the contract in PayMe instead (`error_code` `11000`). ## Step 7: Attach invoices and other files Either seller on the contract can attach files to it, such as an invoice or a receipt. Send the content Base64-encoded with [Upload contract files](/b2b-payments/contracts/post-contracts-files): ```bash curl -X POST https://sandbox.payme.io/api/payment-contracts/CONT1700-000000AA-EXAMPLE1-CONT0001/invoices \ -H 'Content-Type: application/json' \ -H 'PayMe-Merchant-Key: MPLDEMO-MPLDEMO-MPLDEMO-1234567' \ -d '{ "invoices": [ { "name": "invoice-1001.pdf", "type": "Invoice Receipt", "base64": "JVBERi0xLjQK", "mime_type": "application/pdf" } ] }' ``` The response lists the files under `Invoices` (capital `I`), each with its `id` (`INV…`) and a `url`. Store the `id`. - On a one-time contract, one request can carry one file of each `type` (error `386`). File names must be unique within a request (error `387`). - Both sellers see every file: [List contract files](/b2b-payments/contracts/get-contracts-files). - Only the seller that uploaded a file can [replace](/b2b-payments/contracts/put-contracts-files) it, with the same `type` (error `388`), or [delete](/b2b-payments/contracts/delete-contracts-files) it. Another seller gets error `385`. - A delete also needs a JSON body that isn't empty, for example `{"confirm": true}`. ## Errors and what to do Contract calls return `status_code` `1`, with the code in `status_error_code` and the message in `status_error_details`. Check `status_code`, not only the HTTP status. These are the contract codes: | Code | When | What to do | |---|---|---| | `380` | No contract with this `guid` involves your seller account. Also returned when a seller that isn't the initiator tries to cancel | Check the `guid` and the MPL in `PayMe-Merchant-Key` | | `381` | Only `recipient.business_code` was sent and it matches no PayMe seller | Add the recipient's `email` or `phone` | | `382` | The action isn't allowed in the contract's current status, for example cancelling a completed contract | Read the contract and act on its `status` | | `383` | The recipient is the initiator's own account | Name another seller | | `384` | The recipient's seller account is inactive | The recipient contacts PayMe support | | `385` | Your seller account isn't a side of this contract, or didn't upload this file | Call with the MPL of a seller on the contract, or of the file's uploader | | `386` | Two files of the same `type`, or an unknown `type`, in one upload | Send one file of each type per request | | `387` | Two files with the same `name` in one upload | Rename one of them | | `388` | A replacement file has a different `type` | Keep the file's `type`, or upload a new file | | `389` | The payer has no card or direct-debit authorization of this method attached | Do [step 2](#step-2-prepare-the-payers-card-or-direct-debit) first | | `390` | No attached card is valid on `payment.due_date` | Attach a card that is valid on the due date, or move the date in a new contract | | `391` | No direct-debit authorization is `Active` | Wait for the bank's approval, or use another method | | `392` | `buyer_key` isn't one of the payer's attached payment methods | Take the `buyer_key` from `get-sellers` for the payer | | `393` | A `credit-card` or `il-direct-debit` contract over `5000000` | Lower the amount, or use `funds-transfer` | | `394` | `buyer_key` sent with `funds-transfer` | Remove `buyer_key` | Other codes you may meet: `1` (no seller key), `251` (unknown seller key), `163` (inactive seller), `252` (currency not enabled for one of the sellers), `25` (invalid `recipient.phone`), `20` (validation, HTTP `422`; `status_additional_info` lists every message) and `900`/`901` (the body isn't valid JSON). The full tables are on [Create a payment contract](/b2b-payments/contracts/post-user) and [B2B payment contracts](/b2b-payments/contracts/overview#errors). ## Before you go live - [ ] Calls go to `https://live.payme.io/api` with production MPLs, and the MPLs stay on your server. - [ ] For card and direct-debit contracts, the payer's payment method is attached in production, and a card is valid on the due date. - [ ] `callback_url` is a public HTTPS address, and your handler replies `200` within 5 seconds. - [ ] Partners verify `payme_signature`. Sellers without a partner client key re-read the contract with `GET /payment-contracts/{guid}`. - [ ] You store each `guid` and reconcile with [List payment contracts](/b2b-payments/contracts/get-contracts), because a callback is dropped after its last attempt.