ChatKomodo Docs Home Sign in

API reference · Customers

QRIS payments

Send a QRIS invoice to a chat and get paid into your own Pakasir account (Indonesia, rupiah). The customer gets the QR code with the total and a payment page; when it is paid (confirmed with Pakasir), they get a thank-you message, a label, and their lead moves to the won stage. The invoice.paid webhook event follows.

Payment settings

GET/v1/payments/settings

Whether your Pakasir account is connected (the API key is never returned), the webhook URL to set in Pakasir, and what happens when an invoice is paid.

Responses

  • 200Settings
    Response fields
    • connectedbooleanrequired
    • providerstringrequired
      One of: pakasir
    • slugstring | nullrequired
    • has_webhook_secretbooleanrequired
    • webhook_urlstring | nullrequired
      Put this as the webhook URL of your Pakasir project. Without it, unpaid invoices are checked every 2 minutes
    • on_paidobject | nullrequired
    • on_paid.stagestring | nullrequired
      Lead stage when paid: a stage id, auto (the first won stage) or null (leave it)
    • on_paid.labelstring | nullrequired
      Label put on the person when paid (null = none)
    • on_paid.thank_youstring | nullrequired
      Message sent to the customer when paid; {total}, {amount}, {description}, {order} are filled in. null = none
    • on_paid.set_valuebooleanrequired
      Use the amount as the deal value when the lead has none (rupiah leads)
    • min_amountintegerrequired
    • max_amountintegerrequired
Request
curl "https://api.chatkomodo.com/v1/payments/settings" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/payments/settings', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.get(
    "https://api.chatkomodo.com/v1/payments/settings",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/payments/settings', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "connected": true,
  "provider": "pakasir",
  "slug": "string",
  "has_webhook_secret": true,
  "webhook_url": "https://example.com/webhooks/chatkomodo",
  "on_paid": {
    "stage": "vip",
    "label": "string",
    "thank_you": "string",
    "set_value": true
  },
  "min_amount": 150000,
  "max_amount": 150000
}

Connect Pakasir / change settings

PUT/v1/payments/settings

Your Pakasir project slug and api_key (kept encrypted), an optional webhook_secret (Pakasir sends it as X-Secret; null removes it) and on_paid. Send only what changes. Business owner only. Recorded as payments.settings.

Request body

  • slugstring
  • api_keystring
  • webhook_secretstring | null
  • on_paidany

Responses

  • 200Settings
    Response fields
    • connectedbooleanrequired
    • providerstringrequired
      One of: pakasir
    • slugstring | nullrequired
    • has_webhook_secretbooleanrequired
    • webhook_urlstring | nullrequired
      Put this as the webhook URL of your Pakasir project. Without it, unpaid invoices are checked every 2 minutes
    • on_paidobject | nullrequired
    • on_paid.stagestring | nullrequired
      Lead stage when paid: a stage id, auto (the first won stage) or null (leave it)
    • on_paid.labelstring | nullrequired
      Label put on the person when paid (null = none)
    • on_paid.thank_youstring | nullrequired
      Message sent to the customer when paid; {total}, {amount}, {description}, {order} are filled in. null = none
    • on_paid.set_valuebooleanrequired
      Use the amount as the deal value when the lead has none (rupiah leads)
    • min_amountintegerrequired
    • max_amountintegerrequired
  • 400VALIDATION_FAILED
  • 403FORBIDDEN: not the owner
Request
curl -X PUT "https://api.chatkomodo.com/v1/payments/settings" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "slug": "tokoani",
  "api_key": "YOUR_PAKASIR_API_KEY",
  "webhook_secret": "a-secret-you-set-in-pakasir",
  "on_paid": {
    "stage": "auto",
    "label": "paid",
    "thank_you": "Thank you! We have received {total} for {description}.",
    "set_value": true
  }
}'
const res = await fetch('https://api.chatkomodo.com/v1/payments/settings', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "slug": "tokoani",
    "api_key": "YOUR_PAKASIR_API_KEY",
    "webhook_secret": "a-secret-you-set-in-pakasir",
    "on_paid": {
      "stage": "auto",
      "label": "paid",
      "thank_you": "Thank you! We have received {total} for {description}.",
      "set_value": true
    }
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.put(
    "https://api.chatkomodo.com/v1/payments/settings",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "slug": "tokoani",
        "api_key": "YOUR_PAKASIR_API_KEY",
        "webhook_secret": "a-secret-you-set-in-pakasir",
        "on_paid": {
            "stage": "auto",
            "label": "paid",
            "thank_you": "Thank you! We have received {total} for {description}.",
            "set_value": True,
        },
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('PUT', 'https://api.chatkomodo.com/v1/payments/settings', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'slug' => 'tokoani',
        'api_key' => 'YOUR_PAKASIR_API_KEY',
        'webhook_secret' => 'a-secret-you-set-in-pakasir',
        'on_paid' => [
            'stage' => 'auto',
            'label' => 'paid',
            'thank_you' => 'Thank you! We have received {total} for {description}.',
            'set_value' => true,
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "connected": true,
  "provider": "pakasir",
  "slug": "string",
  "has_webhook_secret": true,
  "webhook_url": "https://example.com/webhooks/chatkomodo",
  "on_paid": {
    "stage": "vip",
    "label": "string",
    "thank_you": "string",
    "set_value": true
  },
  "min_amount": 150000,
  "max_amount": 150000
}

Disconnect Pakasir

DEL/v1/payments/settings

Removes the account; existing invoices are kept but are no longer checked. Recorded as payments.disconnect.

Responses

  • 200Done
    Response fields
    • okbooleanrequired
      One of: true
  • 403FORBIDDEN: not the owner
Request
curl -X DELETE "https://api.chatkomodo.com/v1/payments/settings" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/payments/settings', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.delete(
    "https://api.chatkomodo.com/v1/payments/settings",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('DELETE', 'https://api.chatkomodo.com/v1/payments/settings', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true
}

List invoices

GET/v1/payments/invoices

Newest first, with totals: paid in the last 30 days (sandbox excluded) and still pending. Filter by status or phone.

Query parameters

  • statusstring
    One of: pending, paid, expired, cancelled
  • phonestring
  • limitinteger
    From 1 to 200

Responses

  • 200Invoices
    Response fields
    • invoicesarray of objectrequired
    • invoices[].idstringrequired
    • invoices[].order_idstringrequired
      Shown to the customer and in Pakasir
    • invoices[].session_idstringrequired
    • invoices[].chat_idstringrequired
    • invoices[].phonestring | nullrequired
    • invoices[].descriptionstringrequired
    • invoices[].amountintegerrequired
      Rupiah, what you receive
    • invoices[].feeintegerrequired
      Pakasir's fee, added for the customer
    • invoices[].totalintegerrequired
      What the customer pays
    • invoices[].currencystringrequired
      One of: IDR
    • invoices[].statusstringrequired
      One of: pending, paid, expired, cancelled
    • invoices[].sandboxbooleanrequired
      Paid in Pakasir's sandbox (a test)
    • invoices[].pay_urlstringrequired
      The payment page with the QR code
    • invoices[].expires_atinteger | nullrequired
      Unix time in milliseconds
    • invoices[].paid_atinteger | nullrequired
      Unix time in milliseconds
    • invoices[].created_atintegerrequired
      Unix time in milliseconds
    • invoices[].created_bystring | nullrequired
      Agent id, owner or api-key
    • totalsobjectrequired
    • totals.paid_30dintegerrequired
    • totals.paid_amount_30dintegerrequired
    • totals.pendingintegerrequired
    • totals.pending_amountintegerrequired
    • currencystringrequired
      One of: IDR
Request
curl "https://api.chatkomodo.com/v1/payments/invoices" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/payments/invoices', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.get(
    "https://api.chatkomodo.com/v1/payments/invoices",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/payments/invoices', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "invoices": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "session_id": "YOUR_DEVICE_ID",
      "chat_id": "+447700900123",
      "phone": "+447700900123",
      "description": "Short description",
      "amount": 150000,
      "fee": 1,
      "total": 12,
      "currency": "IDR",
      "status": "pending",
      "sandbox": true,
      "pay_url": "https://example.com/file.jpg",
      "expires_at": 1767225600000,
      "paid_at": 1767225600000,
      "created_at": 1767225600000,
      "created_by": "string"
    }
  ],
  "totals": {
    "paid_30d": 1,
    "paid_amount_30d": 150000,
    "pending": 1,
    "pending_amount": 150000
  },
  "currency": "IDR"
}

Send a QRIS invoice

POST/v1/payments/invoices

Creates a QRIS payment in your Pakasir account for amount rupiah (1 000 to 100 000 000; Pakasir adds its fee for the customer) and, unless send is false, sends the QR image with the total and a link to the payment page to the chat. When it is paid (webhook or the 2-minute check, always confirmed with Pakasir), the person moves to the won stage, gets the label and the thank-you message from the settings, and the invoice.paid webhook event fires. Recorded as invoice.create.

Request body

  • session_idstringrequired
  • chat_idstring
    A chat with a person
  • phonestring
    Instead of chat_id
  • amountintegerrequired
  • descriptionstringrequired
    Up to 200 characters
  • sendboolean

Responses

  • 201The invoice
    Response fields
    • idstringrequired
    • order_idstringrequired
      Shown to the customer and in Pakasir
    • session_idstringrequired
    • chat_idstringrequired
    • phonestring | nullrequired
    • descriptionstringrequired
    • amountintegerrequired
      Rupiah, what you receive
    • feeintegerrequired
      Pakasir's fee, added for the customer
    • totalintegerrequired
      What the customer pays
    • currencystringrequired
      One of: IDR
    • statusstringrequired
      One of: pending, paid, expired, cancelled
    • sandboxbooleanrequired
      Paid in Pakasir's sandbox (a test)
    • pay_urlstringrequired
      The payment page with the QR code
    • expires_atinteger | nullrequired
      Unix time in milliseconds
    • paid_atinteger | nullrequired
      Unix time in milliseconds
    • created_atintegerrequired
      Unix time in milliseconds
    • created_bystring | nullrequired
      Agent id, owner or api-key
  • 400VALIDATION_FAILED
  • 409PAYMENTS_NOT_CONNECTED
  • 502PAYMENT_PROVIDER_ERROR: Pakasir refused
  • 503PAYMENT_PROVIDER_ERROR: Pakasir unreachable; try again
Request
curl -X POST "https://api.chatkomodo.com/v1/payments/invoices" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "session_id": "YOUR_DEVICE_ID",
  "phone": "+6281234567890",
  "amount": 150000,
  "description": "Order #1042"
}'
const res = await fetch('https://api.chatkomodo.com/v1/payments/invoices', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "session_id": "YOUR_DEVICE_ID",
    "phone": "+6281234567890",
    "amount": 150000,
    "description": "Order #1042"
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.post(
    "https://api.chatkomodo.com/v1/payments/invoices",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "session_id": "YOUR_DEVICE_ID",
        "phone": "+6281234567890",
        "amount": 150000,
        "description": "Order #1042",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/payments/invoices', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'session_id' => 'YOUR_DEVICE_ID',
        'phone' => '+6281234567890',
        'amount' => 150000,
        'description' => 'Order #1042',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 201
{
  "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "session_id": "YOUR_DEVICE_ID",
  "chat_id": "+447700900123",
  "phone": "+447700900123",
  "description": "Short description",
  "amount": 150000,
  "fee": 1,
  "total": 12,
  "currency": "IDR",
  "status": "pending",
  "sandbox": true,
  "pay_url": "https://example.com/file.jpg",
  "expires_at": 1767225600000,
  "paid_at": 1767225600000,
  "created_at": 1767225600000,
  "created_by": "string"
}

Get an invoice

GET/v1/payments/invoices/{id}

One invoice with its status and payment page.

Path parameters

  • idstringrequired

Responses

  • 200The invoice
    Response fields
    • idstringrequired
    • order_idstringrequired
      Shown to the customer and in Pakasir
    • session_idstringrequired
    • chat_idstringrequired
    • phonestring | nullrequired
    • descriptionstringrequired
    • amountintegerrequired
      Rupiah, what you receive
    • feeintegerrequired
      Pakasir's fee, added for the customer
    • totalintegerrequired
      What the customer pays
    • currencystringrequired
      One of: IDR
    • statusstringrequired
      One of: pending, paid, expired, cancelled
    • sandboxbooleanrequired
      Paid in Pakasir's sandbox (a test)
    • pay_urlstringrequired
      The payment page with the QR code
    • expires_atinteger | nullrequired
      Unix time in milliseconds
    • paid_atinteger | nullrequired
      Unix time in milliseconds
    • created_atintegerrequired
      Unix time in milliseconds
    • created_bystring | nullrequired
      Agent id, owner or api-key
  • 404NOT_FOUND
Request
curl "https://api.chatkomodo.com/v1/payments/invoices/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/payments/invoices/ID', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.get(
    "https://api.chatkomodo.com/v1/payments/invoices/ID",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/payments/invoices/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "session_id": "YOUR_DEVICE_ID",
  "chat_id": "+447700900123",
  "phone": "+447700900123",
  "description": "Short description",
  "amount": 150000,
  "fee": 1,
  "total": 12,
  "currency": "IDR",
  "status": "pending",
  "sandbox": true,
  "pay_url": "https://example.com/file.jpg",
  "expires_at": 1767225600000,
  "paid_at": 1767225600000,
  "created_at": 1767225600000,
  "created_by": "string"
}

Check an invoice now

POST/v1/payments/invoices/{id}/check

Asks Pakasir for the status right away (at most every 5 seconds) and applies a payment.

Path parameters

  • idstringrequired

Responses

  • 200The invoice
    Response fields
    • idstringrequired
    • order_idstringrequired
      Shown to the customer and in Pakasir
    • session_idstringrequired
    • chat_idstringrequired
    • phonestring | nullrequired
    • descriptionstringrequired
    • amountintegerrequired
      Rupiah, what you receive
    • feeintegerrequired
      Pakasir's fee, added for the customer
    • totalintegerrequired
      What the customer pays
    • currencystringrequired
      One of: IDR
    • statusstringrequired
      One of: pending, paid, expired, cancelled
    • sandboxbooleanrequired
      Paid in Pakasir's sandbox (a test)
    • pay_urlstringrequired
      The payment page with the QR code
    • expires_atinteger | nullrequired
      Unix time in milliseconds
    • paid_atinteger | nullrequired
      Unix time in milliseconds
    • created_atintegerrequired
      Unix time in milliseconds
    • created_bystring | nullrequired
      Agent id, owner or api-key
  • 404NOT_FOUND
Request
curl -X POST "https://api.chatkomodo.com/v1/payments/invoices/ID/check" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/payments/invoices/ID/check', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.post(
    "https://api.chatkomodo.com/v1/payments/invoices/ID/check",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/payments/invoices/ID/check', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "session_id": "YOUR_DEVICE_ID",
  "chat_id": "+447700900123",
  "phone": "+447700900123",
  "description": "Short description",
  "amount": 150000,
  "fee": 1,
  "total": 12,
  "currency": "IDR",
  "status": "pending",
  "sandbox": true,
  "pay_url": "https://example.com/file.jpg",
  "expires_at": 1767225600000,
  "paid_at": 1767225600000,
  "created_at": 1767225600000,
  "created_by": "string"
}

Cancel an invoice

POST/v1/payments/invoices/{id}/cancel

Cancels an unpaid invoice at Pakasir (and its message, if still queued). If it turns out to be paid, it is marked paid instead. Recorded as invoice.cancel.

Path parameters

  • idstringrequired

Responses

  • 200The invoice
    Response fields
    • idstringrequired
    • order_idstringrequired
      Shown to the customer and in Pakasir
    • session_idstringrequired
    • chat_idstringrequired
    • phonestring | nullrequired
    • descriptionstringrequired
    • amountintegerrequired
      Rupiah, what you receive
    • feeintegerrequired
      Pakasir's fee, added for the customer
    • totalintegerrequired
      What the customer pays
    • currencystringrequired
      One of: IDR
    • statusstringrequired
      One of: pending, paid, expired, cancelled
    • sandboxbooleanrequired
      Paid in Pakasir's sandbox (a test)
    • pay_urlstringrequired
      The payment page with the QR code
    • expires_atinteger | nullrequired
      Unix time in milliseconds
    • paid_atinteger | nullrequired
      Unix time in milliseconds
    • created_atintegerrequired
      Unix time in milliseconds
    • created_bystring | nullrequired
      Agent id, owner or api-key
  • 404NOT_FOUND
  • 409CONFLICT: not pending
Request
curl -X POST "https://api.chatkomodo.com/v1/payments/invoices/ID/cancel" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/payments/invoices/ID/cancel', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.post(
    "https://api.chatkomodo.com/v1/payments/invoices/ID/cancel",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/payments/invoices/ID/cancel', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "session_id": "YOUR_DEVICE_ID",
  "chat_id": "+447700900123",
  "phone": "+447700900123",
  "description": "Short description",
  "amount": 150000,
  "fee": 1,
  "total": 12,
  "currency": "IDR",
  "status": "pending",
  "sandbox": true,
  "pay_url": "https://example.com/file.jpg",
  "expires_at": 1767225600000,
  "paid_at": 1767225600000,
  "created_at": 1767225600000,
  "created_by": "string"
}

Payment page (customer)

GET/public/invoice/{token}No authentication

The page the customer opens from the invoice message (https://chatkomodo.com/pay/{token} shows it): business, description, total, the QR code and a PNG to save, refreshing until paid. No authentication.

Path parameters

  • tokenstringrequired

Responses

  • 200HTML
  • 404Unknown invoice (HTML)
Request
curl "https://api.chatkomodo.com/public/invoice/YOUR_STORE_TOKEN"
const res = await fetch('https://api.chatkomodo.com/public/invoice/YOUR_STORE_TOKEN', {
  method: 'GET',
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.get(
    "https://api.chatkomodo.com/public/invoice/YOUR_STORE_TOKEN",
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/public/invoice/YOUR_STORE_TOKEN', [
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200

HTML

Pakasir webhook (your project)

POST/public/pay/pakasir/{hook}No authentication

Where your Pakasir project posts payments (webhook_url in the settings). Checks X-Secret when you set one; the payment is then confirmed with Pakasir's status API. No authentication.

Path parameters

  • hookstringrequired

Request body

  • order_idstringrequired

Responses

  • 200Received
    Response fields
    • okbooleanrequired
      One of: true
    • statusstring
  • 401Wrong X-Secret
  • 404Unknown webhook address
Request
curl -X POST "https://api.chatkomodo.com/public/pay/pakasir/HOOK" \
  -H "Content-Type: application/json" \
  -d '{
  "order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X"
}'
const res = await fetch('https://api.chatkomodo.com/public/pay/pakasir/HOOK', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X"
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.post(
    "https://api.chatkomodo.com/public/pay/pakasir/HOOK",
    json={
        "order_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/public/pay/pakasir/HOOK', [
    'json' => [
        'order_id' => '01JA5V6Q8M2H7G4K9P3T0R1S2X',
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true,
  "status": "active"
}