Kommo + Adyen: Enterprise Payments from the Sales Pipeline with Automatic Reconciliation

Adyen is an enterprise payment platform used by Uber, Spotify, McDonald’s, and Booking.com: a unified gateway for online payments, in-store (POS), in-app, and marketplace payouts. Its key advantages over Stripe or Recurly include global acquiring (direct agreements with payment networks without intermediaries), built-in reconciliation via Adyen Data for financial reporting, and marketplace split payments. For B2B companies with enterprise clients, Adyen is often a contractual standard — not because it’s simpler, but because the contract requires it. Without integration with Kommo Won: payment links are created manually, and managers can’t see payment status in the CRM.

Adyen vs Stripe vs Mollie for enterprise B2B

ParameterAdyenStripeMollie
Acquiring modelDirect (own acquiring)Through partnersThrough partners
Interchange++ pricingYesNoNo
Marketplace paymentsNativeStripe ConnectNo
In-store (POS)Yes (Adyen Terminal)Stripe TerminalNo
Minimum volume~$1M/yearNoneNone
Reconciliation/reportingAdyen Data (built-in)Via dashboardVia dashboard
EU orientationYes (Amsterdam)Yes (Dublin)Yes (Netherlands)

Companies with over $1M/year in volume choose Adyen when direct interchange++ pricing matters and a unified platform for all sales channels is required.

Architecture: Kommo + Adyen

Kommo Won  ->  Python handler  ->  Adyen Payment Links API  ->  link in Note
Adyen Webhook  ->  Python handler  ->  Kommo Note / Deal Stage

Adyen has no connector for Kommo. Integration is built via Adyen API (Management API + Checkout API) and webhooks.

Authentication: Adyen API Key

Adyen uses the X-API-Key header. The API Key is created in Adyen Customer Area -> Developers -> API credentials -> Create new credential.

import requests
import hmac, hashlib, base64

ADYEN_API_KEY    = "your_api_key"
ADYEN_MERCHANT   = "YourMerchantAccount"
ADYEN_BASE       = "https://checkout-test.adyen.com/checkout/v71"
# Production: https://checkout-live.adyen.com/checkout/v71
ADYEN_HEADERS    = {
    "X-API-Key":    ADYEN_API_KEY,
    "Content-Type": "application/json",
}
ADYEN_HMAC_KEY   = "your_hmac_key_hex"  # from Adyen Customer Area -> Webhooks

Adyen Payment Links allow you to create a hosted checkout link without PCI responsibility on your side:

from datetime import datetime, timezone, timedelta

def create_adyen_payment_link(amount_cents: int, currency: str,
                               description: str, reference: str,
                               email: str = "") -> dict:
    # reference - unique identifier, use Kommo deal ID
    expires_at = (
        datetime.now(timezone.utc) + timedelta(days=7)
    ).strftime("%Y-%m-%dT%H:%M:%S+00:00")

    payload = {
        "merchantAccount": ADYEN_MERCHANT,
        "amount": {
            "currency": currency,
            "value":    amount_cents,
        },
        "reference":   reference,
        "description": description,
        "expiresAt":   expires_at,
        "returnUrl":   "https://yoursite.com/payment-complete",
    }
    if email:
        payload["shopperEmail"] = email

    resp = requests.post(
        f"{ADYEN_BASE}/paymentLinks",
        headers=ADYEN_HEADERS,
        json=payload,
    )
    resp.raise_for_status()
    return resp.json()

def on_kommo_deal_won(lead: dict, contact: dict):
    price       = int((lead.get("price") or 0) * 100)  # cents
    currency    = get_custom_field(lead, CURRENCY_FIELD_ID) or "EUR"
    reference   = f"kommo-{lead['id']}"
    description = lead.get("name", "")
    email       = get_contact_email(contact)

    link_data  = create_adyen_payment_link(
        amount_cents=price,
        currency=currency,
        description=description,
        reference=reference,
        email=email,
    )
    link_url   = link_data.get("url", "")
    link_id    = link_data.get("id", "")

    save_to_kommo_deal(lead["id"], {
        "adyen_payment_link_id": link_id,
        "adyen_reference":       reference,
    })
    create_kommo_note(
        lead["id"],
        f"Adyen: payment link created -> {link_url} (expires in 7 days)",
    )

Webhook: Adyen -> Kommo

Adyen webhooks use HMAC-SHA256 signing. Setup: Customer Area -> Developers -> Webhooks -> Standard webhook.

def verify_adyen_hmac(payload: dict, hmac_key_hex: str,
                       received_hmac: str) -> bool:
    # Adyen HMAC verification
    fields = [
        "pspReference", "originalReference", "merchantAccountCode",
        "merchantReference", "value", "currency", "eventCode", "success",
    ]
    hmac_key  = bytes.fromhex(hmac_key_hex)
    data      = ":".join(str(payload.get(f, "")) for f in fields)
    computed  = base64.b64encode(
        hmac.new(hmac_key, data.encode("utf-8"), hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(computed, received_hmac)

@app.route("/webhooks/adyen", methods=["POST"])
def adyen_webhook():
    body = request.json
    for item in body.get("notificationItems", []):
        notification = item.get("NotificationRequestItem", {})
        received_hmac = notification.get("additionalData", {}).get("hmacSignature", "")

        if not verify_adyen_hmac(notification, ADYEN_HMAC_KEY, received_hmac):
            return "[accepted]", 401

        event_code = notification.get("eventCode", "")
        reference  = notification.get("merchantReference", "")
        success    = notification.get("success") == "true"

        lead_id = find_kommo_deal_by_custom_field("adyen_reference", reference)
        if not lead_id:
            continue

        if event_code == "AUTHORISATION" and success:
            amount    = notification.get("amount", {})
            value_str = f"{amount.get('value', 0) / 100:.2f} {amount.get('currency', '')}"
            create_kommo_note(lead_id,
                f"Adyen: payment authorized - {value_str}")

        elif event_code == "CAPTURE" and success:
            amount    = notification.get("amount", {})
            value_str = f"{amount.get('value', 0) / 100:.2f} {amount.get('currency', '')}"
            create_kommo_note(lead_id,
                f"Adyen: payment captured - {value_str}")
            move_kommo_deal_to_stage(lead_id, PAID_STAGE_ID)

        elif event_code == "REFUND" and success:
            create_kommo_note(lead_id, "Adyen: refund processed")

        elif event_code == "CHARGEBACK":
            create_kommo_note(lead_id, "Adyen: chargeback - action required")
            create_kommo_task(lead_id, "Adyen: handle chargeback")

    return "[accepted]", 200

Important: Adyen requires the response body [accepted] (a string) with HTTP 200, otherwise it retries the webhook up to 10 times.

Marketplace split payments

If you have a marketplace model (selling on behalf of multiple vendors), Adyen supports automatic payment splitting via the splits object:

def create_split_payment_link(amount_cents: int, currency: str,
                               reference: str, splits: list) -> dict:
    payload = {
        "merchantAccount": ADYEN_MERCHANT,
        "amount":  {"currency": currency, "value": amount_cents},
        "reference": reference,
        "splits":    splits,
    }
    resp = requests.post(
        f"{ADYEN_BASE}/paymentLinks",
        headers=ADYEN_HEADERS,
        json=payload,
    )
    resp.raise_for_status()
    return resp.json()

# splits example: 90% to vendor, 10% marketplace commission
SPLITS_EXAMPLE = [
    {"type": "MarketPlace", "amount": {"value": 9000}, "account": "vendor_account_code"},
    {"type": "Commission",  "amount": {"value": 1000}},
]

Real-world case study

B2B SaaS marketplace (EU, 200+ enterprise clients, Kommo + Adyen):

  • Before: Won -> finance team manually created a payment request in Adyen Customer Area and emailed the link. 1–2 business days of delay. Manager couldn’t see payment status without logging into Adyen.
  • After: Won -> Python webhook -> Adyen Payment Link in 2 seconds -> link in Note. CAPTURE event -> Note “paid” + deal stage moved to “Paid.” Manager sees the full cycle in Kommo without opening Adyen.
  • Additionally: CHARGEBACK event -> Note + Task -> manager responds the same day. Before integration, chargeback notifications only reached the CFO by email.

Who benefits from this integration

  • Enterprise B2B with Adyen as a contractual standard (the contract specifies a particular gateway)
  • Marketplace companies that need automatic split payments between vendors and the platform
  • Companies with multi-currency sales in EU/US/APAC — Adyen supports 150+ currencies natively
  • Teams where sales managers need to see payment status in the CRM without access to Adyen Customer Area

Frequently asked questions

Adyen test vs production — how to switch?

Adyen provides a test environment (Customer Area Test) and production (Customer Area Live). URLs differ: checkout-test.adyen.com vs checkout-live.adyen.com. API Keys are different for test and live. In code: use an environment variable ADYEN_ENV = "test" | "live" to switch the base URL and API key.

A Payment Link is created with an expiresAt parameter (ISO 8601). Once expired, the link is invalid. To request payment again, create a new Link via POST /paymentLinks. Check existing Link status: GET /paymentLinks/{id} — the status field can be active, expired, or completed. On expired -> automatically create a new one via webhook or cron job.

How does Adyen handle EU Strong Customer Authentication (SCA)?

Adyen natively supports 3DS2 for SCA. Payment Links automatically include a 3DS challenge when required by the client’s bank. No additional configuration is needed — Adyen determines whether a challenge is necessary through an AI model (exemptions: transaction risk analysis). For B2B payments with corporate cards, SCA is often exempt through a TRA (Transaction Risk Analysis) exemption.

Adyen webhooks vs Adyen Reports — which to use for reconciliation?

Webhooks: real-time events (AUTHORISATION, CAPTURE, REFUND). Adyen Reports: batch files (CSV) available daily via SFTP or Adyen Reporting API for accounting reconciliation. For Kommo integration — use webhooks. For ERP/accounting — use Reports API. Both can be used in parallel.

Summary

  • Auth: X-API-Key header, not Bearer token
  • Payment Link: POST /checkout/v71/paymentLinks, reference = Kommo deal ID
  • Webhook: respond with [accepted] (string), HMAC-SHA256 verification is mandatory
  • Key events: CAPTURE (funds received), CHARGEBACK (task for manager), REFUND
  • Adyen split payments for marketplace: splits object in payload

If you use Adyen and Kommo and want payment status visible in the deal card — describe your payment model (single payments or marketplace). Exceltic.dev will set up two-way integration with HMAC verification.

More articles

All →