Run a B2B payment contract end to end
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
- Create a payment contract
- List payment contracts
- List previous recipients
- Upload contract files
- List contract files
- Save a card (token sale)
- Attach a saved card to a seller
- List sellers
- Callbacks
- Callback types reference
- Transfer funds between sellers
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, and each step links to the reference page with the full field tables.
Every call in this guide:
- goes to
https://sandbox.payme.io/apiwhile you test, and tohttps://live.payme.io/apiin production; - authenticates with the seller's
seller_payme_id(MPL) in thePayMe-Merchant-Keyheader. A partner acting for one of its sellers sends that seller's MPL, and can add its own client key in thePayMe-Partner-Keyheader; - 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.
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:
- Tokenize the card with
generate-saleandsale_typetoken. Thesale-completecallback carries the card'sbuyer_key. - Attach the
buyer_keyto the initiator's seller account withadd-card-to-seller. - Check that the card appears in
seller_credit_cardsofget-sellers. It must still be valid on the contract'spayment.due_date(error390).
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.
Step 3: Create the contract
Call POST /payment-contracts. This contract sends 5,000.00 by card on December 22:
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):
{
"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.
Name the recipient.
- To pay one specific PayMe seller, send its MPL as
recipient.payme_id, and no otherrecipientfield. For a recipient of your earlier contracts, take itsseller_payme_id_checksumfrom List previous recipients. - Otherwise send
emailorphone, 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 preferpayme_idwhen you know it. business_codealone must match a PayMe seller (error381).
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.
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:
{
"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
2doesn't last: when the recipient approves, PayMe confirms the initiator side at once, so the contract moves on to3and waits for the due date. You receive both callbacks. - Payment. PayMe runs an approved contract once
payment.due_datehas passed. A completed contract is final. - Failure. Status
5always carrieserror_codeanderror_text. Readerror_codeto know why a contract failed, notnotify_type: a contract that wasn't approved in time can arrive aspayment-contract-payment-failedtoo. On11004,error_noteholds the payment error as free text. The codes are on 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 also lists
payment-contract-initiator-rejected. Handle it like any status5callback: readerror_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
200within 5 seconds, then do the slow work. Delivery, retries and ordering work as for sale callbacks: see Callbacks. - Callbacks can arrive more than once and out of order. Act on
status, and don't let a late callback overwrite a status4or5you already recorded. - Partners verify
payme_signaturewith the contract formula on B2B payment contracts. A seller without a partner client key re-reads the contract instead (below). - Only the initiator's
callback_urlreceives callbacks. The recipient side follows its contracts with List payment contracts andrecipient=true.
Read a contract
Read one contract with GET /payment-contracts/{guid}. Either seller on the contract can call it:
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, 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_keytogether withpayment.method. The same rules as on create apply (errors389to394).
For example, to pay with another attached card:
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:
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
5witherror_code11001(Initiator Canceled), and PayMe sendspayment-contract-canceled. The response is the contract, at the top level and underdata. - Send a body: a request without JSON fails with error
900, and an empty one with901. - 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_code11000).
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:
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(error386). File names must be unique within a request (error387). - Both sellers see every file: List contract files.
- Only the seller that uploaded a file can replace it, with the same
type(error388), or delete it. Another seller gets error385. - 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 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 and B2B payment contracts.
Before you go live
- Calls go to
https://live.payme.io/apiwith 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_urlis a public HTTPS address, and your handler replies200within 5 seconds. - Partners verify
payme_signature. Sellers without a partner client key re-read the contract withGET /payment-contracts/{guid}. - You store each
guidand reconcile with List payment contracts, because a callback is dropped after its last attempt.