Wise Business API позволяет создавать международные переводы программно: выбрать получателя, указать сумму и валюту, подтвердить - и деньги уходят без ручного перехода в банк. Для агентств и консалтинговых компаний, которые платят подрядчикам из Kommo-воронки, интеграция закрывает стандартный разрыв: менеджер переводит сделку на этап “Оплатить”, и платёж создаётся автоматически.
Wise API использует Bearer token (API Key из Wise Business -> Developer Tools). Основной flow из нескольких вызовов: GET /v3/profiles -> POST /v3/quotes -> POST /v3/accounts -> POST /v3/transfers -> POST /v3/transfers/{id}/payments. Каждый шаг зависит от результата предыдущего - именно поэтому Zapier не справляется с этой задачей.
Wise Business - API-доступный сервис международных переводов с поддержкой 80+ валют и multi-currency балансом. Отличается от Wise Personal: Business-аккаунт требует верификации компании, открывает полный API и batch-платежи.
Почему Zapier не закрывает эту задачу
Wise-коннектор в Zapier поддерживает только простые одношаговые операции. Transfer flow требует минимум 4-5 API-вызовов в строгой последовательности: сначала получить profileId, потом создать quote (он привязан к конкретной сумме и курсу обмена), потом убедиться что получатель существует, потом создать transfer и отдельно его подтвердить. Ни один low-code инструмент не моделирует такой граф зависимостей без кастомного кода.
Типовой масштаб: агентство с 20-30 выплатами подрядчикам в месяц тратит 2-3 часа на ручные переводы. Интеграция сводит это к одному действию менеджера в Kommo.
Архитектура
Kommo: сделка -> этап "Оплатить подрядчика"
-> Kommo webhook leads.status.changed
-> Ваш сервер
Ваш сервер:
1. GET /v3/profiles -> profileId
2. POST /v3/profiles/{profileId}/quotes -> quoteId
3. GET или POST /v3/accounts -> recipientAccountId (кэш в Kommo)
4. POST /v3/transfers -> transferId
5. POST /v3/profiles/{profileId}/transfers/{transferId}/payments -> отправить
-> Kommo: note с transferId и статусом
RecipientAccountId кэшируется в кастомном поле Kommo-контакта. При повторных выплатах одному подрядчику шаги 3 пропускается.
Реализация
import requests, os, uuid
from flask import Flask, request, jsonify
app = Flask(__name__)
WISE_API_KEY = os.environ["WISE_API_KEY"]
WISE_BASE = "https://api.transferwise.com"
WISE_HDR = {"Authorization": f"Bearer {WISE_API_KEY}",
"Content-Type": "application/json"}
KOMMO_SUBDOMAIN = os.environ["KOMMO_SUBDOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
PAYOUT_STAGE_ID = int(os.environ["KOMMO_PAYOUT_STAGE_ID"])
KOMMO_BASE = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4"
KOMMO_HDR = {"Authorization": f"Bearer {KOMMO_TOKEN}",
"Content-Type": "application/json"}
CF_WISE_ACCOUNT_ID = int(os.environ["CF_WISE_ACCOUNT_ID"])
CF_PAYOUT_AMOUNT = int(os.environ["CF_PAYOUT_AMOUNT"])
CF_PAYOUT_CURRENCY = int(os.environ["CF_PAYOUT_CURRENCY"])
def get_profile_id() -> int:
r = requests.get(f"{WISE_BASE}/v3/profiles", headers=WISE_HDR)
r.raise_for_status()
for p in r.json():
if p["type"] == "BUSINESS":
return p["id"]
raise ValueError("No BUSINESS profile")
def create_quote(profile_id: int, source_cur: str,
target_cur: str, amount: float) -> str:
r = requests.post(
f"{WISE_BASE}/v3/profiles/{profile_id}/quotes",
headers=WISE_HDR,
json={
"sourceCurrency": source_cur,
"targetCurrency": target_cur,
"targetAmount": amount,
"payOut": "BANK_TRANSFER",
},
)
r.raise_for_status()
return r.json()["id"]
def get_or_create_recipient(profile_id: int, contact: dict) -> int:
existing = get_cf(contact, CF_WISE_ACCOUNT_ID)
if existing:
return int(existing)
name = contact.get("name", "")
email = get_email(contact)
r = requests.post(
f"{WISE_BASE}/v3/accounts",
headers=WISE_HDR,
json={
"currency": "USD",
"type": "email",
"profile": profile_id,
"accountHolderName": name,
"details": {"email": email},
},
)
r.raise_for_status()
account_id = r.json()["id"]
save_cf(contact["id"], CF_WISE_ACCOUNT_ID, str(account_id))
return account_id
def create_transfer(quote_id: str, recipient_id: int, lead_id: int) -> int:
r = requests.post(
f"{WISE_BASE}/v3/transfers",
headers=WISE_HDR,
json={
"targetAccount": recipient_id,
"quoteUuid": quote_id,
"customerTransactionId": str(uuid.uuid4()),
"details": {"reference": f"Kommo deal #{lead_id}"},
},
)
r.raise_for_status()
return r.json()["id"]
def fund_transfer(profile_id: int, transfer_id: int) -> str:
r = requests.post(
f"{WISE_BASE}/v3/profiles/{profile_id}/transfers/{transfer_id}/payments",
headers=WISE_HDR,
json={"type": "BALANCE"},
)
r.raise_for_status()
return r.json().get("status", "unknown")
def get_cf(entity: dict, field_id: int) -> str:
for cf in entity.get("custom_fields_values", []) or []:
if cf.get("field_id") == field_id:
vals = cf.get("values", [])
return vals[0].get("value", "") if vals else ""
return ""
def get_email(contact: dict) -> str:
for cf in contact.get("custom_fields_values", []) or []:
if cf.get("field_code") == "EMAIL":
vals = cf.get("values", [])
return vals[0].get("value", "") if vals else ""
return ""
def save_cf(contact_id: int, field_id: int, value: str):
requests.patch(
f"{KOMMO_BASE}/contacts/{contact_id}",
headers=KOMMO_HDR,
json={"custom_fields_values": [
{"field_id": field_id, "values": [{"value": value}]}
]},
)
def add_note(lead_id: int, text: str):
requests.post(
f"{KOMMO_BASE}/notes",
headers=KOMMO_HDR,
json=[{"entity_id": lead_id, "entity_type": "leads",
"note_type": "common", "params": {"text": text}}],
)
def get_lead_contact(lead_id: int) -> tuple:
r = requests.get(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
params={"with": "contacts,custom_fields_values"},
)
lead = r.json()
contacts = lead.get("_embedded", {}).get("contacts", [])
contact = {}
if contacts:
rc = requests.get(
f"{KOMMO_BASE}/contacts/{contacts[0]['id']}",
headers=KOMMO_HDR,
params={"with": "custom_fields_values"},
)
contact = rc.json()
return lead, contact
@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
data = request.json or {}
for lead_data in data.get("leads", {}).get("status", []):
lead_id = lead_data.get("id")
new_status = lead_data.get("status_id")
if new_status != PAYOUT_STAGE_ID:
continue
lead, contact = get_lead_contact(lead_id)
amount = float(get_cf(lead, CF_PAYOUT_AMOUNT) or lead.get("price") or 0)
currency = get_cf(lead, CF_PAYOUT_CURRENCY) or "USD"
if amount <= 0:
add_note(lead_id, "Wise: сумма выплаты не задана.")
continue
profile_id = get_profile_id()
quote_id = create_quote(profile_id, "USD", currency, amount)
recipient = get_or_create_recipient(profile_id, contact)
transfer_id = create_transfer(quote_id, recipient, lead_id)
status = fund_transfer(profile_id, transfer_id)
add_note(lead_id,
f"Wise перевод #{transfer_id}: {amount} {currency}, статус: {status}")
return jsonify({"status": "ok"}), 200
Webhook Wise для отслеживания статуса
Wise отправляет события о статусе перевода через подписки. Создать подписку:
def subscribe_wise_webhooks(profile_id: int,
callback_url: str, secret: str) -> str:
r = requests.post(
f"{WISE_BASE}/v3/subscriptions",
headers=WISE_HDR,
json={
"name": "Kommo transfer updates",
"trigger_on": "transfers#state-change",
"delivery": {
"version": "2.0",
"url": callback_url,
"signature": {
"type": "SHA256_HMAC",
"token": secret,
},
},
"scope": {
"domain": "profile",
"id": str(profile_id),
},
},
)
r.raise_for_status()
return r.json()["id"]
@app.route("/webhooks/wise", methods=["POST"])
def wise_webhook():
import hmac, hashlib
secret = os.environ["WISE_WEBHOOK_SECRET"]
sig = request.headers.get("X-Signature-SHA256", "")
body = request.get_data()
expected = hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(sig, expected):
return jsonify({"error": "invalid signature"}), 401
event = request.json or {}
if event.get("event_type") != "transfers#state-change":
return jsonify({"status": "ignored"}), 200
resource = event.get("data", {}).get("resource", {})
t_id = resource.get("id")
new_state = resource.get("current_state")
# outgoing_payment_sent = деньги отправлены
# funds_refunded = возврат
print(f"Transfer {t_id} -> {new_state}")
return jsonify({"status": "ok"}), 200
Ключевые состояния: processing, funds_converted, outgoing_payment_sent, funds_refunded.
Sandbox для тестирования
Wise предоставляет sandbox: sandbox.transferwise.com. Создайте отдельный ключ в Wise Business -> Developer Tools -> Sandbox. Все переводы в sandbox не требуют реальных средств и можно протестировать весь flow, включая webhook-события.
Batch-выплаты
Для 10+ выплат в день используйте POST /v3/profiles/{profileId}/batch-payments - один запрос с массивом transferId вместо N отдельных payment-вызовов. Transfer’ы создаются по одному (каждый с уникальным customerTransactionId), а подтверждаются одним batch-запросом.
Для кого актуально
Агентства и консалтинговые компании с международными подрядчиками: дизайнерами в EU, разработчиками в UK/Canada/Australia, фрилансерами по всему миру. Типовой сценарий: проект выигран в воронке Kommo, сделка закрыта, нужно выплатить гонорар - без перехода в банк и без ручной работы. Особенно актуально при 15+ выплатах в месяц, когда ручная работа становится систематической потерей времени.
Альтернативные платёжные интеграции: Kommo + Razorpay (India), Kommo + Flutterwave (Африка).
Часто задаваемые вопросы
Можно ли создавать переводы без ручного подтверждения?
Да. Шаг POST .../transfers/{id}/payments с {"type": "BALANCE"} подтверждает перевод автоматически. Предварительное условие: достаточный баланс в исходной валюте. Добавьте проверку баланса через GET /v4/profiles/{profileId}/balances перед созданием transfer.
Как хранить wise_account_id подрядчика в Kommo?
Создайте кастомное поле типа “Текст” в разделе Kontakты Kommo. При первом создании перевода запишите recipientAccountId в это поле через PATCH /api/v4/contacts/{id}. При последующих выплатах тому же подрядчику - читайте поле и пропускайте шаг POST /v3/accounts.
Что если на балансе Wise недостаточно средств?
Wise вернёт HTTP 422 с кодом INSUFFICIENT_BALANCE на шаге payments. Обработайте это: поймайте ошибку, добавьте note в Kommo “Wise: пополните баланс для перевода {amount} {currency}” и выйдите без crash. Мониторинг баланса - отдельная задача через Wise API или уведомления в Wise Dashboard.
Поддерживает ли Wise transfers на криптокошельки?
Нет. Wise - только банковские переводы, IBAN, SWIFT, местные схемы (ACH, SEPA, Faster Payments). Для крипто-выплат нужен отдельный сервис (Coinbase Commerce, BitPay).
Итог
Kommo + Wise Business - автоматические выплаты подрядчикам:
- Bearer token, обязательный flow: profile -> quote -> account -> transfer -> payment
- Кэшировать
recipientAccountIdв кастомном поле Kommo-контакта - Webhook
transfers#state-changeчерез SHA256_HMAC-подписку - Sandbox:
sandbox.transferwise.comдля полного тестирования без реальных денег - 80+ валют, mid-market rate, прозрачная комиссия
Если ваша команда платит международным подрядчикам через Kommo - опишите задачу команде Exceltic.dev. Разберём архитектуру под ваш стек.