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:
KIRAK_PAYMENT_OMISE_SECRET_KEY=skey_test_...KIRAK_PAYMENT_OMISE_WEBHOOK_SECRET=<the base64 webhook secret> # recommendedFor 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 paidawait 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_paymentafter the buyer returns. - Subscriptions, saved payment methods and off-session charges are not supported yet.
Webhook events
Section titled “Webhook events”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.