Skip to content

Omise

Setup guide for the omise payments provider. The shared API (initiate_payment, verify_payment, refund_payment, webhooks, hooks, saved methods) is documented in Payments.

The omise provider (Omise, now Opn Payments) supports one-time payments through Omise Links (initiate_payment, verify_payment; cards, PromptPay, TrueMoney, internet banking, konbini and the other methods your account enables), refunds (refund_payment), reading disputes and webhooks, in THB, JPY, SGD, MYR, USD and the other currencies your account supports. GET /payments/providers reports dispute_read.

Feature Status
One-time payments (Links), verify, refunds Supported
get_dispute, dispute webhooks Supported
submit_dispute_evidence Not supported (NOT_SUPPORTED, 501): respond in the Omise dashboard
Subscriptions, billing portal Not supported (NOT_SUPPORTED, 501): Omise schedules need a saved card
Saved payment methods, charge_off_session Not supported yet (NOT_SUPPORTED, 501)

It needs no extra install: it calls https://api.omise.co directly over HTTP, following Omise’s API reference as of September 2026.

Why plain HTTP (no Omise SDK). Omise’s Python package, omise, keeps the API key in a module-global variable (omise.api_secret), so two Omise instances in one app would share one key.

1. Get credentials. In the Omise Dashboard, Keys, copy the secret key (skey_test_... in test mode, skey_... in live mode).

2. Set the webhook. In Webhooks, set the endpoint to https://your-domain.com/payments/webhook/omise (/payments/webhook/<instance> for another instance name), and generate a webhook secret (base64). Omise does not guarantee retries of a failed delivery, so also call verify_payment when the buyer returns (below).

3. Configure the instance and secrets. In kirak.json:

"omise": { "type": "omise", "environment": "test" }

environment is test (the default) or live and must match the secret key (skey_test_ or a live skey_), else a CONFIGURATION_ERROR at startup. In the environment:

Terminal window
KIRAK_PAYMENT_OMISE_SECRET_KEY=skey_test_...
KIRAK_PAYMENT_OMISE_WEBHOOK_SECRET=<the base64 webhook secret> # recommended

For an instance with another name the variables are KIRAK_PAYMENT_<INSTANCE>_SECRET_KEY (required) and KIRAK_PAYMENT_<INSTANCE>_WEBHOOK_SECRET (optional).

4. Use it:

result = await kirak.payments.initiate_payment({
"provider": "omise",
"user_id": "42", "amount": 100000, "currency": "THB", # satang: THB 1,000.00
"name": "Pro Plan", "type": "one_time",
})
link_id = result["data"]["reference"] # the Omise link id
# When the buyer returns: completes the row if the link was paid
await kirak.payments.verify_payment({"provider": "omise", "payment_id": link_id})
await kirak.payments.refund_payment({"provider": "omise", "payment_id": link_id, "amount": 40000})

Payments. initiate_payment saves the PENDING row, creates a single-use link (amount x quantity in the currency’s smallest unit, title = name) and stores the link id as the row’s transaction_id before returning its payment_uri as url. A 4xx answer marks the row FAILED; a timeout, 5xx or 429 leaves it PENDING (the link never reached the buyer).

A charge.complete webhook is never trusted alone: the charge is re-read, and the row of its link completes once (payment_completed, with amount and currency) only for a successful, paid charge in the row’s currency for at least amount x quantity. A failed charge keeps the row PENDING (the link can be paid again). Because Omise does not guarantee webhook retries, verify_payment (payment_id = the link id) reads the link’s charges and completes the row itself when one is confirmed. It reports no event: the first charge.complete for that charge still reports payment_completed once, so fulfil on the event and use verify_payment’s status only for the return page.

Refunds. refund_payment (payment_id = the link id; optional amount) calls POST /charges/{id}/refunds for the completing charge; Omise refunds at once, so the refund is recorded now (keyed by refund id with a running total; REFUNDED never moves back). refund.create re-reads the refund and records it once (refund_completed), which also covers refunds made in the dashboard.

Disputes (read-only). get_dispute reads GET /disputes/{id}. dispute.create (dispute_created) and dispute.update/.accept/.close (dispute_updated) re-read the dispute and report it for the row its charge completed; the row is not changed.

Webhook security. With a webhook secret, Omise-Signature must hold the hex HMAC-SHA256, with the base64-decoded secret, of <Omise-Signature-Timestamp>.<raw body> (either of two comma-separated signatures during a secret rotation); missing headers are MISSING_WEBHOOK_SIGNATURE, a wrong one INVALID_WEBHOOK_SIGNATURE (400). Without a secret, each delivery is verified by reading its event back (GET /events/{id}, same key and object). Deliveries are deduplicated on the event id.

Currencies. Omise amounts are integers in the currency’s smallest unit with ISO 4217 decimals (satang for THB; JPY has none), as Kirak stores them.

Known limits.

  • Omise does not guarantee webhook retries: call verify_payment after the buyer returns.
  • Subscriptions, saved payment methods and off-session charges are not supported yet.

after_webhook receives these provider-neutral event names (see Webhook Hooks):

Omise reports payment_completed (charge.complete, once the re-read charge confirms it; a failed attempt reports none, with charge.failed_attempt), refund_completed (refund.create, event_type refund.recorded), dispute_created (dispute.create) and dispute_updated (dispute.update, .accept, .close); verify_payment reports no event, but a payment it completed is reported by the first charge.complete for its charge.