KulPayDOCS LIVE Integration guide OpenAPI JSON

Event catalogue

Every change KulPay makes is recorded as an event, in the same transaction as the change itself: if the change happened, its event exists, and the other way round. 406 event types are defined below, each with the JSON Schema its data is validated against before it is stored.

The envelope

Every event, whether delivered by webhook or read from the Events API, has the same shape.

{
  "id": "2mZp4T0…",                 // unique; the same in the webhook and in /v1/events/{id}
  "type": "payment.succeeded",
  "schema_version": 1,
  "aggregate_type": "payment",
  "aggregate_id": "2mZp3x…",       // the object the event is about
  "sequence": 2,                    // strictly increasing per object: order by this
  "occurred_at": "2026-10-08T09:12:44.120Z",
  "recorded_at": "2026-10-08T09:12:44.121Z",
  "created_at": "2026-10-08T09:12:44.121Z",
  "tenant_id": "2mYq…",
  "mode": "live",
  "region": "mz",
  "direction": "internal",          // internal, inbound or outbound
  "actor": { "type": "api_key", "id": "key_…", "ip": "203.0.113.9" },
  "correlation_id": "req_…",        // the request that caused it (X-Request-ID)
  "causation_id": "",               // the event that caused it, if any
  "idempotency_key": "order-77",
  "data": { … }                     // validated against the type's schema
}

Catching up

Webhooks are retried, but an endpoint can still miss events (an outage longer than the retry window, a bug in your handler). Keep the id of the last event you processed and ask for everything after it:

GET /v1/events?since=<last event id>&limit=100
Authorization: Bearer <key with events:read>

Results are oldest first; repeat with next_cursor while has_more is true. The newest two seconds are held back so an event can never be skipped by a transaction that was still committing. To browse instead, omit since: results are newest first and filter by type, aggregate_id, created[gte] and created[lte].

account events

EventWebhookTerminalFires whenWhat to do
account.created v1 no no A tenant account was created. Audit record. Compare before and after.
account.deposit_tag.set v1 no no An account's cash-deposit tag was set or cleared (PSP-7.6): the short code depositors may quote instead of the account ID. Tell depositors the new tag.
account.status_changed v1 no no A tenant account changed status (for example suspended). Audit record. Compare before and after.
bill_balance.low v1 yes no A channel's balance, from which its bill payments are paid, fell below the threshold set for it (BIL-3.4). Sent once until it is back above. Top up your balance (a cash deposit or a transfer) before payments are refused with insufficient_funds.
credential.mtls.required v1 no no An account now requires, or no longer requires, a valid client certificate issued to it on every API call (PSP-8.7). Audit record.
partner.alert v1 yes no An operational alert to the partner (P-2), also sent by email to its contacts: an API key or client certificate expiring, a key found in public, a statement not received. alert names it; subject and body are the email text. Route it to your on-call; act on what the body says.
reserve.released v1 yes no Rolling-reserve holds of an account fell due and were released to its available balance (F2-6): holds is how many, amount what came back (what was left of them after covering refunds or disputes), still_held what the account still holds in reserve. The released amount is available, and joins the next settlement.
tenant.region.assigned v1 no no A tenant was assigned the region its data is stored and processed in: at creation, or by an approved migration. Compliance record. A change of region names its approval.

account.created

A tenant account was created. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/account.created/v1",
  "title": "account.created",
  "description": "A tenant account was created.",
  "x-kulpay": {
    "aggregate": "account",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/account.json"
    },
    "after": {
      "$ref": "kulpay://objects/account.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

account.deposit_tag.set

An account's cash-deposit tag was set or cleared (PSP-7.6): the short code depositors may quote instead of the account ID. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/account.deposit_tag.set/v1",
  "title": "account.deposit_tag.set",
  "description": "An account's cash-deposit tag was set or cleared (PSP-7.6): the short code depositors may quote instead of the account ID.",
  "x-kulpay": {
    "aggregate": "account",
    "webhook": false,
    "terminal": false,
    "act": "Tell depositors the new tag."
  },
  "type": "object",
  "required": [
    "account_id",
    "tag"
  ],
  "properties": {
    "account_id": {
      "type": "string"
    },
    "tag": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

account.status_changed

A tenant account changed status (for example suspended). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/account.status_changed/v1",
  "title": "account.status_changed",
  "description": "A tenant account changed status (for example suspended).",
  "x-kulpay": {
    "aggregate": "account",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "before",
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/account.json"
    },
    "after": {
      "$ref": "kulpay://objects/account.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

bill_balance.low

A channel's balance, from which its bill payments are paid, fell below the threshold set for it (BIL-3.4). Sent once until it is back above. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_balance.low/v1",
  "title": "bill_balance.low",
  "description": "A channel's balance, from which its bill payments are paid, fell below the threshold set for it (BIL-3.4). Sent once until it is back above.",
  "x-kulpay": {
    "aggregate": "account",
    "webhook": true,
    "terminal": false,
    "act": "Top up your balance (a cash deposit or a transfer) before payments are refused with insufficient_funds."
  },
  "type": "object",
  "required": [
    "account_id",
    "balance_minor",
    "threshold_minor",
    "currency"
  ],
  "properties": {
    "account_id": {
      "type": "string"
    },
    "balance_minor": {
      "type": "integer"
    },
    "threshold_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

credential.mtls.required

An account now requires, or no longer requires, a valid client certificate issued to it on every API call (PSP-8.7). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.mtls.required/v1",
  "title": "credential.mtls.required",
  "description": "An account now requires, or no longer requires, a valid client certificate issued to it on every API call (PSP-8.7).",
  "x-kulpay": {
    "aggregate": "account",
    "webhook": false,
    "terminal": false,
    "act": "Audit record."
  },
  "type": "object",
  "required": [
    "account_id",
    "required"
  ],
  "properties": {
    "account_id": {
      "type": "string"
    },
    "required": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

partner.alert

An operational alert to the partner (P-2), also sent by email to its contacts: an API key or client certificate expiring, a key found in public, a statement not received. alert names it; subject and body are the email text. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/partner.alert/v1",
  "title": "partner.alert",
  "description": "An operational alert to the partner (P-2), also sent by email to its contacts: an API key or client certificate expiring, a key found in public, a statement not received. alert names it; subject and body are the email text.",
  "x-kulpay": {
    "aggregate": "account",
    "webhook": true,
    "terminal": false,
    "act": "Route it to your on-call; act on what the body says."
  },
  "type": "object",
  "required": [
    "notification_id",
    "alert",
    "subject",
    "body",
    "language"
  ],
  "properties": {
    "notification_id": {
      "type": "string"
    },
    "alert": {
      "type": "string",
      "examples": [
        "api_key.expiring",
        "api_key.leaked",
        "certificate.expiring",
        "recon.file.missing"
      ]
    },
    "subject": {
      "type": "string"
    },
    "body": {
      "type": "string"
    },
    "language": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

reserve.released

Rolling-reserve holds of an account fell due and were released to its available balance (F2-6): holds is how many, amount what came back (what was left of them after covering refunds or disputes), still_held what the account still holds in reserve. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reserve.released/v1",
  "title": "reserve.released",
  "description": "Rolling-reserve holds of an account fell due and were released to its available balance (F2-6): holds is how many, amount what came back (what was left of them after covering refunds or disputes), still_held what the account still holds in reserve.",
  "x-kulpay": {
    "aggregate": "account",
    "webhook": true,
    "terminal": false,
    "act": "The released amount is available, and joins the next settlement."
  },
  "type": "object",
  "required": [
    "account_id",
    "currency",
    "holds",
    "amount",
    "still_held"
  ],
  "properties": {
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "holds": {
      "type": "integer"
    },
    "amount": {
      "type": "string"
    },
    "still_held": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

tenant.region.assigned

A tenant was assigned the region its data is stored and processed in: at creation, or by an approved migration. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/tenant.region.assigned/v1",
  "title": "tenant.region.assigned",
  "description": "A tenant was assigned the region its data is stored and processed in: at creation, or by an approved migration.",
  "x-kulpay": {
    "aggregate": "account",
    "webhook": false,
    "terminal": false,
    "act": "Compliance record. A change of region names its approval."
  },
  "type": "object",
  "required": [
    "account_id",
    "region"
  ],
  "properties": {
    "account_id": {
      "type": "string"
    },
    "region": {
      "type": "string"
    },
    "previous_region": {
      "type": "string"
    },
    "approval_ref": {
      "type": "string",
      "maxLength": 200,
      "description": "The approved migration this change belongs to."
    }
  },
  "additionalProperties": false
}

approval events

EventWebhookTerminalFires whenWhat to do
approval.approved v1 no no A checker approved a decision; it took effect. None.
approval.rejected v1 no no A checker rejected a decision; nothing changed. None.
approval.requested v1 no no A maker prepared a decision that needs a second person. A different officer approves or rejects it.

approval.approved

A checker approved a decision; it took effect. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/approval.approved/v1",
  "title": "approval.approved",
  "description": "A checker approved a decision; it took effect.",
  "x-kulpay": {
    "aggregate": "approval",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "approval_id",
    "kind",
    "subject_id",
    "prepared_by",
    "decided_by"
  ],
  "properties": {
    "approval_id": {
      "type": "string"
    },
    "kind": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "prepared_by": {
      "type": "string"
    },
    "decided_by": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

approval.rejected

A checker rejected a decision; nothing changed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/approval.rejected/v1",
  "title": "approval.rejected",
  "description": "A checker rejected a decision; nothing changed.",
  "x-kulpay": {
    "aggregate": "approval",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "approval_id",
    "kind",
    "subject_id",
    "prepared_by",
    "decided_by"
  ],
  "properties": {
    "approval_id": {
      "type": "string"
    },
    "kind": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "prepared_by": {
      "type": "string"
    },
    "decided_by": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

approval.requested

A maker prepared a decision that needs a second person. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/approval.requested/v1",
  "title": "approval.requested",
  "description": "A maker prepared a decision that needs a second person.",
  "x-kulpay": {
    "aggregate": "approval",
    "webhook": false,
    "terminal": false,
    "act": "A different officer approves or rejects it."
  },
  "type": "object",
  "required": [
    "approval_id",
    "kind",
    "subject_id",
    "prepared_by",
    "reason"
  ],
  "properties": {
    "approval_id": {
      "type": "string"
    },
    "kind": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "prepared_by": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

audit events

EventWebhookTerminalFires whenWhat to do
audit.anchor.created v1 no no The day's chain heads were signed and stored in write-once storage. Operators only.
audit.anchor.failed v1 no no Creating the day's anchor failed; it is retried hourly. Operators: see runbook 6.
audit.chain.broken v1 no no An audit chain failed verification: an event was altered, removed or reordered after it was written. Operators: follow runbook 3 immediately.
audit.chain.verified v1 no no An hourly verification pass checked every chain. Operators only.
audit.export.created v1 no no An audit export bundle was produced. Audit record.

audit.anchor.created

The day's chain heads were signed and stored in write-once storage. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/audit.anchor.created/v1",
  "title": "audit.anchor.created",
  "description": "The day's chain heads were signed and stored in write-once storage.",
  "x-kulpay": {
    "aggregate": "audit",
    "webhook": false,
    "terminal": false,
    "act": "Operators only."
  },
  "type": "object",
  "required": [
    "day",
    "merkle_root",
    "chains",
    "signed"
  ],
  "properties": {
    "day": {
      "type": "string"
    },
    "merkle_root": {
      "type": "string"
    },
    "chains": {
      "type": "integer"
    },
    "signed": {
      "type": "boolean"
    },
    "key_id": {
      "type": "string"
    },
    "object_key": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

audit.anchor.failed

Creating the day's anchor failed; it is retried hourly. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/audit.anchor.failed/v1",
  "title": "audit.anchor.failed",
  "description": "Creating the day's anchor failed; it is retried hourly.",
  "x-kulpay": {
    "aggregate": "audit",
    "webhook": false,
    "terminal": false,
    "act": "Operators: see runbook 6."
  },
  "type": "object",
  "required": [
    "day",
    "error",
    "attempt"
  ],
  "properties": {
    "day": {
      "type": "string"
    },
    "error": {
      "type": "string"
    },
    "attempt": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

audit.chain.broken

An audit chain failed verification: an event was altered, removed or reordered after it was written. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/audit.chain.broken/v1",
  "title": "audit.chain.broken",
  "description": "An audit chain failed verification: an event was altered, removed or reordered after it was written.",
  "x-kulpay": {
    "aggregate": "audit",
    "webhook": false,
    "terminal": false,
    "act": "Operators: follow runbook 3 immediately."
  },
  "type": "object",
  "required": [
    "chain_id",
    "broken_at_seq",
    "reason"
  ],
  "properties": {
    "chain_id": {
      "type": "string"
    },
    "broken_at_seq": {
      "type": "integer"
    },
    "event_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

audit.chain.verified

An hourly verification pass checked every chain. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/audit.chain.verified/v1",
  "title": "audit.chain.verified",
  "description": "An hourly verification pass checked every chain.",
  "x-kulpay": {
    "aggregate": "audit",
    "webhook": false,
    "terminal": false,
    "act": "Operators only."
  },
  "type": "object",
  "required": [
    "chains",
    "events_checked",
    "broken"
  ],
  "properties": {
    "chains": {
      "type": "integer"
    },
    "events_checked": {
      "type": "integer"
    },
    "broken": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

audit.export.created

An audit export bundle was produced. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/audit.export.created/v1",
  "title": "audit.export.created",
  "description": "An audit export bundle was produced.",
  "x-kulpay": {
    "aggregate": "audit",
    "webhook": false,
    "terminal": false,
    "act": "Audit record."
  },
  "type": "object",
  "required": [
    "export_id",
    "tenant_id",
    "from",
    "to",
    "events"
  ],
  "properties": {
    "export_id": {
      "type": "string"
    },
    "tenant_id": {
      "type": "string"
    },
    "from": {
      "type": "string",
      "format": "date-time"
    },
    "to": {
      "type": "string",
      "format": "date-time"
    },
    "events": {
      "type": "integer"
    },
    "http_entries": {
      "type": "integer"
    },
    "sha256": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bank_rail_file events

EventWebhookTerminalFires whenWhat to do
bank_rail.file_received v1 no yes A file from the sponsor bank (a pain.002 status report or a camt.054 notification) was received, kept and applied once to the bank payout rail; result says what it did (F2-1). None; an unknown kind or a mismatch in result needs a look from treasury.

bank_rail.file_received

A file from the sponsor bank (a pain.002 status report or a camt.054 notification) was received, kept and applied once to the bank payout rail; result says what it did (F2-1). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bank_rail.file_received/v1",
  "title": "bank_rail.file_received",
  "description": "A file from the sponsor bank (a pain.002 status report or a camt.054 notification) was received, kept and applied once to the bank payout rail; result says what it did (F2-1).",
  "x-kulpay": {
    "aggregate": "bank_rail_file",
    "webhook": false,
    "terminal": true,
    "act": "None; an unknown kind or a mismatch in result needs a look from treasury."
  },
  "type": "object",
  "required": [
    "file_id",
    "name",
    "kind",
    "sha256",
    "result"
  ],
  "properties": {
    "file_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "kind": {
      "enum": [
        "pain.002",
        "camt.054",
        "unknown"
      ]
    },
    "sha256": {
      "type": "string"
    },
    "result": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_channel events

EventWebhookTerminalFires whenWhat to do
bill_channel.unmasking_changed v1 no no A channel was allowed, under the contract named, to see customers full names in bill lookups, or no longer (F3-17), with four eyes. None.

bill_channel.unmasking_changed

A channel was allowed, under the contract named, to see customers full names in bill lookups, or no longer (F3-17), with four eyes. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_channel.unmasking_changed/v1",
  "title": "bill_channel.unmasking_changed",
  "description": "A channel was allowed, under the contract named, to see customers full names in bill lookups, or no longer (F3-17), with four eyes.",
  "x-kulpay": {
    "aggregate": "bill_channel",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "channel_id",
    "unmasked_names",
    "approval_id"
  ],
  "properties": {
    "channel_id": {
      "type": "string"
    },
    "unmasked_names": {
      "type": "boolean"
    },
    "contract_reference": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payment events

EventWebhookTerminalFires whenWhat to do
bill_commission.accrued v1 no no A channel's commission on a bill payment it sold was accrued against the plan in force when it was quoted (BIL-3.3): positive, KulPay paid the channel's balance; negative, KulPay charged it. None; see the monthly commission statement.
bill_payment.accepted v2 no no A channel paid a quote (BIL-2.4), from its balance or by collecting from the payer first. v2 adds the collecting status (collect-first funding). None.
bill_payment.artifact_resent v1 no no A succeeded bill payment's token, PIN or receipt was sent to the payer again (BIL-2.6), at most three times per payment. The recipient is masked; the artifacts are never in the event. None.
bill_payment.artifacts_revealed v1 no no An operator with the bills:artifacts scope saw a bill payment's tokens or PINs in clear (BIL-2.6), giving a reason. The values are never in the event. Review that the access was justified.
bill_payment.collecting v1 no no The bill payment's total is being collected from the payer first (BIL-2.4): through the payment rails, into the channel's account. The bill is sent once the collection succeeds. Follow the collection (the payment collection_id) for the payer to approve it.
bill_payment.expired v1 no yes A quote expired before it was paid (BIL-2.3); nothing was reserved. Quote again.
bill_payment.failed v1 yes yes The bill payment failed definitively (BIL-2.4): the supplier refused it or could not deliver, and the total went back to the channel's balance. Tell the payer; nothing was charged.
bill_payment.pending v1 yes no The supplier gave no definitive answer (a timeout or an ambiguous code), or is still processing (BIL-2.4, BIL-2.5). KulPay queries the same request id until it knows; nothing is sent again. Wait for bill_payment.succeeded or bill_payment.failed; do not pay again.
bill_payment.quoted v1 no no A channel was quoted a bill payment (BIL-2.3): the amount, the payer's fee and the total, pinned to a supply connector and the product's version until it expires. None.
bill_payment.refunded v2 yes yes A bill payment collected from the payer first was refunded to the payer: it could not be delivered (BIL-2.4, status failed) or it was reversed (F3-16, status reversed). v2: status may be reversed. Tell the payer they were refunded.
bill_payment.reserved v1 no no The bill payment's total was reserved from the channel's balance into bill_clearing (BIL-2.4, BIL-3.1). None.
bill_payment.resolved v1 no no An operator's resolution of a bill payment in review was approved by a second operator (BIL-2.5); bill_payment.succeeded or bill_payment.failed follows in the same transaction. None.
bill_payment.reversed v1 yes yes A succeeded payment was reversed by KulPay operators with four eyes (F3-16): the total is back in your balance and, for a payment collected from the payer first, the payer is refunded (bill_payment.refunded follows). Its invoice, if any, is open again. Tell the payer the payment was reversed; refund them yourself when you funded it from your balance.
bill_payment.review_required v1 no no A bill payment of unknown outcome was not settled by querying its supplier within 24 hours (BIL-2.5): it waits in the review queue, its total still reserved, for an operator to establish what the supplier did. Ask the supplier, then resolve it in the admin (a second operator approves).
bill_payment.sending v1 no no The bill payment was sent to its supplier with a deterministic request id, persisted before the call (BIL-2.4). None.
bill_payment.succeeded v3 yes yes The supplier delivered (BIL-2.4): tokens, PINs or a receipt are on the bill payment (GET /v1/bill-payments/{id}), never in the event. The total moved from bill_clearing to the biller's or supplier's payable and fee revenue. v2 (BEV-2): participants names the PSPs in the payment and their roles. v3 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it). Give the payer their token, PIN or receipt.

bill_commission.accrued

A channel's commission on a bill payment it sold was accrued against the plan in force when it was quoted (BIL-3.3): positive, KulPay paid the channel's balance; negative, KulPay charged it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_commission.accrued/v1",
  "title": "bill_commission.accrued",
  "description": "A channel's commission on a bill payment it sold was accrued against the plan in force when it was quoted (BIL-3.3): positive, KulPay paid the channel's balance; negative, KulPay charged it.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "None; see the monthly commission statement."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "amount_minor",
    "currency",
    "plan_id",
    "direction",
    "commission_minor"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "collecting",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "plan_id": {
      "type": "string"
    },
    "plan_version": {
      "type": "integer"
    },
    "direction": {
      "enum": [
        "we_pay",
        "we_charge"
      ]
    },
    "commission_minor": {
      "type": "integer"
    },
    "month_volume_minor": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

bill_payment.accepted

A channel paid a quote (BIL-2.4), from its balance or by collecting from the payer first. v2 adds the collecting status (collect-first funding). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.accepted/v2",
  "title": "bill_payment.accepted",
  "description": "A channel paid a quote (BIL-2.4), from its balance or by collecting from the payer first. v2 adds the collecting status (collect-first funding).",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "funding"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "collecting",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "funding": {
      "enum": [
        "balance",
        "collect"
      ]
    }
  },
  "additionalProperties": false
}

bill_payment.artifact_resent

A succeeded bill payment's token, PIN or receipt was sent to the payer again (BIL-2.6), at most three times per payment. The recipient is masked; the artifacts are never in the event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.artifact_resent/v1",
  "title": "bill_payment.artifact_resent",
  "description": "A succeeded bill payment's token, PIN or receipt was sent to the payer again (BIL-2.6), at most three times per payment. The recipient is masked; the artifacts are never in the event.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "via",
    "to_masked",
    "resends"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "via": {
      "enum": [
        "sms",
        "email"
      ]
    },
    "to_masked": {
      "type": "string"
    },
    "resends": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

bill_payment.artifacts_revealed

An operator with the bills:artifacts scope saw a bill payment's tokens or PINs in clear (BIL-2.6), giving a reason. The values are never in the event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.artifacts_revealed/v1",
  "title": "bill_payment.artifacts_revealed",
  "description": "An operator with the bills:artifacts scope saw a bill payment's tokens or PINs in clear (BIL-2.6), giving a reason. The values are never in the event.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "Review that the access was justified."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "revealed_by",
    "reason"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "revealed_by": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

bill_payment.collecting

The bill payment's total is being collected from the payer first (BIL-2.4): through the payment rails, into the channel's account. The bill is sent once the collection succeeds. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.collecting/v1",
  "title": "bill_payment.collecting",
  "description": "The bill payment's total is being collected from the payer first (BIL-2.4): through the payment rails, into the channel's account. The bill is sent once the collection succeeds.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "Follow the collection (the payment collection_id) for the payer to approve it."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "collection_id"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "collecting",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "collection_id": {
      "type": "string"
    },
    "method": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payment.expired

A quote expired before it was paid (BIL-2.3); nothing was reserved. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.expired/v1",
  "title": "bill_payment.expired",
  "description": "A quote expired before it was paid (BIL-2.3); nothing was reserved.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": true,
    "act": "Quote again."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payment.failed

The bill payment failed definitively (BIL-2.4): the supplier refused it or could not deliver, and the total went back to the channel's balance. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.failed/v1",
  "title": "bill_payment.failed",
  "description": "The bill payment failed definitively (BIL-2.4): the supplier refused it or could not deliver, and the total went back to the channel's balance.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": true,
    "terminal": true,
    "act": "Tell the payer; nothing was charged."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "failure_code"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "failure_code": {
      "type": "string"
    },
    "failure_message": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payment.pending

The supplier gave no definitive answer (a timeout or an ambiguous code), or is still processing (BIL-2.4, BIL-2.5). KulPay queries the same request id until it knows; nothing is sent again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.pending/v1",
  "title": "bill_payment.pending",
  "description": "The supplier gave no definitive answer (a timeout or an ambiguous code), or is still processing (BIL-2.4, BIL-2.5). KulPay queries the same request id until it knows; nothing is sent again.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": true,
    "terminal": false,
    "act": "Wait for bill_payment.succeeded or bill_payment.failed; do not pay again."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "request_id"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "request_id": {
      "type": "string"
    },
    "next_query_at": {
      "type": "string",
      "format": "date-time"
    },
    "queries": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

bill_payment.quoted

A channel was quoted a bill payment (BIL-2.3): the amount, the payer's fee and the total, pinned to a supply connector and the product's version until it expires. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.quoted/v1",
  "title": "bill_payment.quoted",
  "description": "A channel was quoted a bill payment (BIL-2.3): the amount, the payer's fee and the total, pinned to a supply connector and the product's version until it expires.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "biller",
    "product",
    "product_version",
    "connector",
    "quote_expires_at"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "product": {
      "type": "string"
    },
    "product_version": {
      "type": "integer"
    },
    "connector": {
      "type": "string"
    },
    "quote_expires_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

bill_payment.refunded

A bill payment collected from the payer first was refunded to the payer: it could not be delivered (BIL-2.4, status failed) or it was reversed (F3-16, status reversed). v2: status may be reversed. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.refunded/v2",
  "title": "bill_payment.refunded",
  "description": "A bill payment collected from the payer first was refunded to the payer: it could not be delivered (BIL-2.4, status failed) or it was reversed (F3-16, status reversed). v2: status may be reversed.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": true,
    "terminal": true,
    "act": "Tell the payer they were refunded."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "collection_id",
    "refund_id"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "collecting",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review",
        "reversed"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "collection_id": {
      "type": "string"
    },
    "refund_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payment.reserved

The bill payment's total was reserved from the channel's balance into bill_clearing (BIL-2.4, BIL-3.1). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.reserved/v1",
  "title": "bill_payment.reserved",
  "description": "The bill payment's total was reserved from the channel's balance into bill_clearing (BIL-2.4, BIL-3.1).",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payment.resolved

An operator's resolution of a bill payment in review was approved by a second operator (BIL-2.5); bill_payment.succeeded or bill_payment.failed follows in the same transaction. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.resolved/v1",
  "title": "bill_payment.resolved",
  "description": "An operator's resolution of a bill payment in review was approved by a second operator (BIL-2.5); bill_payment.succeeded or bill_payment.failed follows in the same transaction.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "outcome",
    "approval_id"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "outcome": {
      "enum": [
        "succeeded",
        "failed"
      ]
    },
    "approval_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payment.reversed

A succeeded payment was reversed by KulPay operators with four eyes (F3-16): the total is back in your balance and, for a payment collected from the payer first, the payer is refunded (bill_payment.refunded follows). Its invoice, if any, is open again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.reversed/v1",
  "title": "bill_payment.reversed",
  "description": "A succeeded payment was reversed by KulPay operators with four eyes (F3-16): the total is back in your balance and, for a payment collected from the payer first, the payer is refunded (bill_payment.refunded follows). Its invoice, if any, is open again.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": true,
    "terminal": true,
    "act": "Tell the payer the payment was reversed; refund them yourself when you funded it from your balance."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "reason",
    "reversed_at",
    "remitted"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "reversed"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "reversed_at": {
      "type": "string",
      "format": "date-time"
    },
    "approval_id": {
      "type": "string"
    },
    "funding": {
      "type": "string"
    },
    "remitted": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

bill_payment.review_required

A bill payment of unknown outcome was not settled by querying its supplier within 24 hours (BIL-2.5): it waits in the review queue, its total still reserved, for an operator to establish what the supplier did. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.review_required/v1",
  "title": "bill_payment.review_required",
  "description": "A bill payment of unknown outcome was not settled by querying its supplier within 24 hours (BIL-2.5): it waits in the review queue, its total still reserved, for an operator to establish what the supplier did.",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "Ask the supplier, then resolve it in the admin (a second operator approves)."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "request_id"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "request_id": {
      "type": "string"
    },
    "queries": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

bill_payment.sending

The bill payment was sent to its supplier with a deterministic request id, persisted before the call (BIL-2.4). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.sending/v1",
  "title": "bill_payment.sending",
  "description": "The bill payment was sent to its supplier with a deterministic request id, persisted before the call (BIL-2.4).",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "request_id",
    "attempt",
    "connector"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "request_id": {
      "type": "string"
    },
    "attempt": {
      "type": "integer"
    },
    "connector": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payment.succeeded

The supplier delivered (BIL-2.4): tokens, PINs or a receipt are on the bill payment (GET /v1/bill-payments/{id}), never in the event. The total moved from bill_clearing to the biller's or supplier's payable and fee revenue. v2 (BEV-2): participants names the PSPs in the payment and their roles. v3 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it). Schema (JSON)

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.succeeded/v3",
  "title": "bill_payment.succeeded",
  "description": "The supplier delivered (BIL-2.4): tokens, PINs or a receipt are on the bill payment (GET /v1/bill-payments/{id}), never in the event. The total moved from bill_clearing to the biller's or supplier's payable and fee revenue. v2 (BEV-2): participants names the PSPs in the payment and their roles. v3 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it).",
  "x-kulpay": {
    "aggregate": "bill_payment",
    "webhook": true,
    "terminal": true,
    "act": "Give the payer their token, PIN or receipt."
  },
  "type": "object",
  "required": [
    "bill_payment_id",
    "status",
    "shape",
    "amount_minor",
    "payer_fee_minor",
    "total_minor",
    "currency",
    "request_id",
    "participants"
  ],
  "properties": {
    "bill_payment_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "quoted",
        "expired",
        "accepted",
        "reserved",
        "sending",
        "pending",
        "succeeded",
        "failed",
        "review"
      ]
    },
    "shape": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "payer_fee_minor": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string"
    },
    "supplier_reference": {
      "type": "string"
    },
    "request_id": {
      "type": "string"
    },
    "artifact_types": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "participants": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "role",
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "role": {
            "enum": [
              "initiating_psp",
              "from_psp",
              "to_psp"
            ]
          },
          "psp_id": {
            "type": "string"
          },
          "account_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "method": {
            "type": "string"
          },
          "account_masked": {
            "type": "string"
          },
          "settlement": {
            "enum": [
              "prefunded_balance",
              "collected_from_payer",
              "remittance",
              "supplier_float",
              "cash_deposit",
              "kulpay_balance",
              "wallet_payout",
              "unroutable"
            ]
          }
        }
      },
      "minItems": 3,
      "description": "The PSPs in the payment and their roles: initiating_psp (the channel paid through), from_psp (where the money came from), to_psp (where it goes for the biller)."
    }
  },
  "additionalProperties": false
}

bill_connector events

EventWebhookTerminalFires whenWhat to do
bill_connector.configured v1 no no A supply connector, KulPay's own integration with a supplier (EDM, an MNO), was created or changed (BIL-2.1). Only the names of its configuration keys are recorded, never their values. Probe it before routing products to it.
bill_connector.health_changed v1 no no A supply connector's health changed (BIL-4.4), from a probe or a live answer: unauthorized (our credentials or vendor account refused), exhausted (our float), unreachable, degraded, or healthy again. Routing skips unauthorized, exhausted and unreachable connectors. Operations: fix the credentials, top up the float or call the supplier.
bill_float.low v1 no no Our float at a supplier fell below its threshold (BIL-3.4). Alerted once until it is back above. Treasury: top it up and record the top-up.
bill_float.mismatch v1 no no The float a supplier reports differs from what KulPay expects (BIL-3.4): the last reported float, less what was delivered since, plus the top-ups applied, give or take what is in flight. Reconcile the supplier's statement with KulPay's payments and top-ups.
bill_float.topped_up v1 no no A top-up of our float at a supplier was recorded and approved by a second person (BIL-3.4). None.
bill_recon.break_found v1 no no A difference between KulPay and a supplier (BIL-6.3): supplier_only, kulpay_only, amount_mismatch, review_found (the supplier delivered a payment KulPay has pending or in review) or failed_but_delivered. Investigate; resolve review_found payments in the review queue.
bill_recon.completed v1 no no A day of a supply connector's payments was reconciled with the supplier's records (BIL-6.3), from its listing or its report file. Work the breaks.

bill_connector.configured

A supply connector, KulPay's own integration with a supplier (EDM, an MNO), was created or changed (BIL-2.1). Only the names of its configuration keys are recorded, never their values. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_connector.configured/v1",
  "title": "bill_connector.configured",
  "description": "A supply connector, KulPay's own integration with a supplier (EDM, an MNO), was created or changed (BIL-2.1). Only the names of its configuration keys are recorded, never their values.",
  "x-kulpay": {
    "aggregate": "bill_connector",
    "webhook": false,
    "terminal": false,
    "act": "Probe it before routing products to it."
  },
  "type": "object",
  "required": [
    "connector_id",
    "name",
    "driver",
    "mode",
    "enabled",
    "created",
    "config_keys"
  ],
  "properties": {
    "connector_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "driver": {
      "type": "string"
    },
    "mode": {
      "enum": [
        "live",
        "test"
      ]
    },
    "enabled": {
      "type": "boolean"
    },
    "created": {
      "type": "boolean"
    },
    "config_keys": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "changed_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_connector.health_changed

A supply connector's health changed (BIL-4.4), from a probe or a live answer: unauthorized (our credentials or vendor account refused), exhausted (our float), unreachable, degraded, or healthy again. Routing skips unauthorized, exhausted and unreachable connectors. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_connector.health_changed/v1",
  "title": "bill_connector.health_changed",
  "description": "A supply connector's health changed (BIL-4.4), from a probe or a live answer: unauthorized (our credentials or vendor account refused), exhausted (our float), unreachable, degraded, or healthy again. Routing skips unauthorized, exhausted and unreachable connectors.",
  "x-kulpay": {
    "aggregate": "bill_connector",
    "webhook": false,
    "terminal": false,
    "act": "Operations: fix the credentials, top up the float or call the supplier."
  },
  "type": "object",
  "required": [
    "connector_id",
    "name",
    "driver",
    "from",
    "to"
  ],
  "properties": {
    "connector_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "driver": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "enum": [
        "healthy",
        "degraded",
        "unauthorized",
        "exhausted",
        "unreachable"
      ]
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_float.low

Our float at a supplier fell below its threshold (BIL-3.4). Alerted once until it is back above. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_float.low/v1",
  "title": "bill_float.low",
  "description": "Our float at a supplier fell below its threshold (BIL-3.4). Alerted once until it is back above.",
  "x-kulpay": {
    "aggregate": "bill_connector",
    "webhook": false,
    "terminal": false,
    "act": "Treasury: top it up and record the top-up."
  },
  "type": "object",
  "required": [
    "connector_id",
    "name",
    "driver",
    "float_minor",
    "threshold_minor",
    "currency"
  ],
  "properties": {
    "connector_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "driver": {
      "type": "string"
    },
    "float_minor": {
      "type": "integer"
    },
    "threshold_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_float.mismatch

The float a supplier reports differs from what KulPay expects (BIL-3.4): the last reported float, less what was delivered since, plus the top-ups applied, give or take what is in flight. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_float.mismatch/v1",
  "title": "bill_float.mismatch",
  "description": "The float a supplier reports differs from what KulPay expects (BIL-3.4): the last reported float, less what was delivered since, plus the top-ups applied, give or take what is in flight.",
  "x-kulpay": {
    "aggregate": "bill_connector",
    "webhook": false,
    "terminal": false,
    "act": "Reconcile the supplier's statement with KulPay's payments and top-ups."
  },
  "type": "object",
  "required": [
    "connector_id",
    "name",
    "driver",
    "expected_minor",
    "reported_minor",
    "difference_minor",
    "currency"
  ],
  "properties": {
    "connector_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "driver": {
      "type": "string"
    },
    "expected_minor": {
      "type": "integer"
    },
    "reported_minor": {
      "type": "integer"
    },
    "difference_minor": {
      "type": "integer"
    },
    "in_flight_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_float.topped_up

A top-up of our float at a supplier was recorded and approved by a second person (BIL-3.4). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_float.topped_up/v1",
  "title": "bill_float.topped_up",
  "description": "A top-up of our float at a supplier was recorded and approved by a second person (BIL-3.4).",
  "x-kulpay": {
    "aggregate": "bill_connector",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "connector_id",
    "name",
    "driver",
    "topup_id",
    "amount_minor",
    "currency",
    "approval_id"
  ],
  "properties": {
    "connector_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "driver": {
      "type": "string"
    },
    "topup_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "bank_reference": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_recon.break_found

A difference between KulPay and a supplier (BIL-6.3): supplier_only, kulpay_only, amount_mismatch, review_found (the supplier delivered a payment KulPay has pending or in review) or failed_but_delivered. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_recon.break_found/v1",
  "title": "bill_recon.break_found",
  "description": "A difference between KulPay and a supplier (BIL-6.3): supplier_only, kulpay_only, amount_mismatch, review_found (the supplier delivered a payment KulPay has pending or in review) or failed_but_delivered.",
  "x-kulpay": {
    "aggregate": "bill_connector",
    "webhook": false,
    "terminal": false,
    "act": "Investigate; resolve review_found payments in the review queue."
  },
  "type": "object",
  "required": [
    "connector_id",
    "name",
    "driver",
    "run_id",
    "kind"
  ],
  "properties": {
    "connector_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "driver": {
      "type": "string"
    },
    "run_id": {
      "type": "string"
    },
    "kind": {
      "enum": [
        "supplier_only",
        "kulpay_only",
        "amount_mismatch",
        "review_found",
        "failed_but_delivered"
      ]
    },
    "bill_payment_id": {
      "type": "string"
    },
    "supplier_reference": {
      "type": "string"
    },
    "supplier_amount_minor": {
      "type": "integer"
    },
    "kulpay_amount_minor": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

bill_recon.completed

A day of a supply connector's payments was reconciled with the supplier's records (BIL-6.3), from its listing or its report file. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_recon.completed/v1",
  "title": "bill_recon.completed",
  "description": "A day of a supply connector's payments was reconciled with the supplier's records (BIL-6.3), from its listing or its report file.",
  "x-kulpay": {
    "aggregate": "bill_connector",
    "webhook": false,
    "terminal": false,
    "act": "Work the breaks."
  },
  "type": "object",
  "required": [
    "connector_id",
    "name",
    "driver",
    "run_id",
    "day",
    "source",
    "matched",
    "breaks"
  ],
  "properties": {
    "connector_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "driver": {
      "type": "string"
    },
    "run_id": {
      "type": "string"
    },
    "day": {
      "type": "string"
    },
    "source": {
      "enum": [
        "pull",
        "file"
      ]
    },
    "supplier_count": {
      "type": "integer"
    },
    "kulpay_count": {
      "type": "integer"
    },
    "matched": {
      "type": "integer"
    },
    "breaks": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

bill_invoice events

EventWebhookTerminalFires whenWhat to do
bill_invoice.cancelled v2 yes no The biller cancelled an unpaid invoice. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. None.
bill_invoice.delivered v1 yes no A biller presented an invoice for an account one of your payers is subscribed to (BIL-5.6). Pay it by quoting with invoice_id. Show the payer their new bill.
bill_invoice.document_attached v2 yes no The biller attached (or replaced) the invoice's PDF (BDOC-1). v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. None.
bill_invoice.document_removed v2 yes no The biller removed the invoice's PDF (BDOC-1). v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. None.
bill_invoice.expired v2 yes no An unpaid invoice passed its expiry and can no longer be paid. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. Reissue it if it is still owed.
bill_invoice.opened v1 yes no A payer opened the invoice for the first time (BEV-3): reported by the payer's channel, best effort; the payer is named by payer_ref only. None.
bill_invoice.overdue v1 yes no The invoice is unpaid past its due date; told once (BEV-1): {invoice, details}, sent to the biller as a webhook. Follow up with the customer.
bill_invoice.paid v3 yes no The invoice is paid in full; the payment that settled it names its participants (BEV-2). v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. v3 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it). Mark the customer's invoice paid; the money arrives with a remittance.
bill_invoice.partially_paid v2 yes no A payment left the invoice partly paid (the biller accepts partial payments) (BEV-1): {invoice, details}, sent to the biller as a webhook. v2 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it). Show the customer what remains.
bill_invoice.payment_received v2 yes no A payment of the invoice succeeded on a channel, partial or full; one event per payment, with its participants and their roles (BEV-2) (BEV-1): {invoice, details}, sent to the biller as a webhook. v2 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it). Record the payment against the customer's account; reconcile it with the remittance.
bill_invoice.pinned v1 yes no A payer pinned (saved) the invoice (BEV-3): reported by the payer's channel, best effort; the payer is named by payer_ref only. None.
bill_invoice.presented v2 yes no A biller presented an invoice, by API or file. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. None: the invoice is payable on every channel.
bill_invoice.reminded v1 yes no An invoice of one of your payers' subscriptions is due within three days and still open (BIL-5.6). Sent once per invoice. Remind the payer.
bill_invoice.reminder_sent v1 yes no A payer's channel was reminded of the invoice before its due date (BEV-1): {invoice, details}, sent to the biller as a webhook. None.
bill_invoice.remitted v1 yes no Money paid on the invoice left KulPay in a remittance to the biller's bank (BEV-1): {invoice, details}, sent to the biller as a webhook. Match it to the remittance and its report.
bill_invoice.reopened v1 yes no A payment of the invoice was reversed (F3-16): the invoice opens again (paid to open, or partially_paid while other payments remain) and can be paid again. When an earlier remittance already paid you that payment (remitted), the next remittance deducts it. Show the invoice as unpaid again; expect the deduction in the next remittance when remitted is true.
bill_invoice.sent v1 yes no The invoice reached a payer's channel: a subscriber's app, wallet or agent shows it (BEV-1): {invoice, details}, sent to the biller as a webhook. None.
bill_invoice.unpinned v1 yes no A payer unpinned the invoice (BEV-3): reported by the payer's channel, best effort; the payer is named by payer_ref only. None.
bill_invoice.updated v2 yes no An unpaid invoice changed: presented again, or its amount or due date patched. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. Update your copy of the invoice.

bill_invoice.cancelled

The biller cancelled an unpaid invoice. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.cancelled/v2",
  "title": "bill_invoice.cancelled",
  "description": "The biller cancelled an unpaid invoice. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [],
      "properties": {
        "previous_status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.delivered

A biller presented an invoice for an account one of your payers is subscribed to (BIL-5.6). Pay it by quoting with invoice_id. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.delivered/v1",
  "title": "bill_invoice.delivered",
  "description": "A biller presented an invoice for an account one of your payers is subscribed to (BIL-5.6). Pay it by quoting with invoice_id.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Show the payer their new bill."
  },
  "type": "object",
  "required": [
    "invoice_id",
    "biller",
    "number",
    "payer_id",
    "subscription_id",
    "amount_minor",
    "open_minor",
    "currency",
    "due_date"
  ],
  "properties": {
    "invoice_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "biller_name": {
      "type": "string"
    },
    "number": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "open_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "due_date": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_invoice.document_attached

The biller attached (or replaced) the invoice's PDF (BDOC-1). v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.document_attached/v2",
  "title": "bill_invoice.document_attached",
  "description": "The biller attached (or replaced) the invoice's PDF (BDOC-1). v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "file_id",
        "filename",
        "size"
      ],
      "properties": {
        "file_id": {
          "type": "string"
        },
        "filename": {
          "type": "string"
        },
        "size": {
          "type": "integer"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.document_removed

The biller removed the invoice's PDF (BDOC-1). v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.document_removed/v2",
  "title": "bill_invoice.document_removed",
  "description": "The biller removed the invoice's PDF (BDOC-1). v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "file_id"
      ],
      "properties": {
        "file_id": {
          "type": "string"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.expired

An unpaid invoice passed its expiry and can no longer be paid. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.expired/v2",
  "title": "bill_invoice.expired",
  "description": "An unpaid invoice passed its expiry and can no longer be paid. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Reissue it if it is still owed."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "open"
      ],
      "properties": {
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.opened

A payer opened the invoice for the first time (BEV-3): reported by the payer's channel, best effort; the payer is named by payer_ref only. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.opened/v1",
  "title": "bill_invoice.opened",
  "description": "A payer opened the invoice for the first time (BEV-3): reported by the payer's channel, best effort; the payer is named by payer_ref only.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "channel",
        "payer_ref",
        "surface"
      ],
      "properties": {
        "channel": {
          "type": "object",
          "required": [
            "account_id",
            "name"
          ],
          "additionalProperties": false,
          "properties": {
            "account_id": {
              "type": "string"
            },
            "psp_id": {
              "type": "string"
            },
            "name": {
              "type": "string"
            }
          }
        },
        "payer_ref": {
          "type": "string",
          "pattern": "^pr_[0-9a-f]{24}$",
          "description": "An opaque id of the payer, stable per biller and payer; never their name, phone or email."
        },
        "surface": {
          "enum": [
            "app",
            "web",
            "ussd",
            "agent",
            "other"
          ]
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.overdue

The invoice is unpaid past its due date; told once (BEV-1): {invoice, details}, sent to the biller as a webhook. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.overdue/v1",
  "title": "bill_invoice.overdue",
  "description": "The invoice is unpaid past its due date; told once (BEV-1): {invoice, details}, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Follow up with the customer."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "days_overdue",
        "open"
      ],
      "properties": {
        "days_overdue": {
          "type": "integer"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.paid

The invoice is paid in full; the payment that settled it names its participants (BEV-2). v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. v3 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it). Schema (JSON)

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.paid/v3",
  "title": "bill_invoice.paid",
  "description": "The invoice is paid in full; the payment that settled it names its participants (BEV-2). v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. v3 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it).",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Mark the customer's invoice paid; the money arrives with a remittance."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "bill_payment_id",
        "paid_total",
        "overpaid",
        "payments_count",
        "settled_at",
        "participants"
      ],
      "properties": {
        "bill_payment_id": {
          "type": "string"
        },
        "paid_total": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "overpaid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "payments_count": {
          "type": "integer"
        },
        "settled_at": {
          "type": "string"
        },
        "participants": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "role",
              "name"
            ],
            "additionalProperties": false,
            "properties": {
              "role": {
                "enum": [
                  "initiating_psp",
                  "from_psp",
                  "to_psp"
                ]
              },
              "psp_id": {
                "type": "string"
              },
              "account_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "method": {
                "type": "string"
              },
              "account_masked": {
                "type": "string"
              },
              "settlement": {
                "enum": [
                  "prefunded_balance",
                  "collected_from_payer",
                  "remittance",
                  "supplier_float",
                  "cash_deposit",
                  "kulpay_balance",
                  "wallet_payout",
                  "unroutable"
                ]
              }
            }
          },
          "minItems": 3,
          "description": "The PSPs in the payment and their roles: initiating_psp (the channel paid through), from_psp (where the money came from), to_psp (where it goes for the biller)."
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.partially_paid

A payment left the invoice partly paid (the biller accepts partial payments) (BEV-1): {invoice, details}, sent to the biller as a webhook. v2 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.partially_paid/v2",
  "title": "bill_invoice.partially_paid",
  "description": "A payment left the invoice partly paid (the biller accepts partial payments) (BEV-1): {invoice, details}, sent to the biller as a webhook. v2 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it).",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Show the customer what remains."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "bill_payment_id",
        "paid_total",
        "open",
        "payments_count",
        "participants"
      ],
      "properties": {
        "bill_payment_id": {
          "type": "string"
        },
        "paid_total": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "payments_count": {
          "type": "integer"
        },
        "participants": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "role",
              "name"
            ],
            "additionalProperties": false,
            "properties": {
              "role": {
                "enum": [
                  "initiating_psp",
                  "from_psp",
                  "to_psp"
                ]
              },
              "psp_id": {
                "type": "string"
              },
              "account_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "method": {
                "type": "string"
              },
              "account_masked": {
                "type": "string"
              },
              "settlement": {
                "enum": [
                  "prefunded_balance",
                  "collected_from_payer",
                  "remittance",
                  "supplier_float",
                  "cash_deposit",
                  "kulpay_balance",
                  "wallet_payout",
                  "unroutable"
                ]
              }
            }
          },
          "minItems": 3,
          "description": "The PSPs in the payment and their roles: initiating_psp (the channel paid through), from_psp (where the money came from), to_psp (where it goes for the biller)."
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.payment_received

A payment of the invoice succeeded on a channel, partial or full; one event per payment, with its participants and their roles (BEV-2) (BEV-1): {invoice, details}, sent to the biller as a webhook. v2 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.payment_received/v2",
  "title": "bill_invoice.payment_received",
  "description": "A payment of the invoice succeeded on a channel, partial or full; one event per payment, with its participants and their roles (BEV-2) (BEV-1): {invoice, details}, sent to the biller as a webhook. v2 (IO-4.2): a payment funded by a cash deposit names the partner that took the cash (initiating_psp) and the bank that received it (from_psp, settlement cash_deposit); a biller onboarded in KulOffice is paid at its credit account's institution (to_psp: remittance to its bank, wallet_payout to its wallet operator, kulpay_balance, or unroutable when no PSP KulPay knows serves it).",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Record the payment against the customer's account; reconcile it with the remittance."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "bill_payment_id",
        "amount",
        "fee",
        "net",
        "currency",
        "channel",
        "funding",
        "paid_at",
        "participants"
      ],
      "properties": {
        "bill_payment_id": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "fee": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "net": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "payer_fee": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "channel": {
          "type": "object",
          "required": [
            "account_id",
            "name"
          ],
          "additionalProperties": false,
          "properties": {
            "account_id": {
              "type": "string"
            },
            "psp_id": {
              "type": "string"
            },
            "name": {
              "type": "string"
            }
          }
        },
        "funding": {
          "enum": [
            "balance",
            "collect"
          ]
        },
        "paid_at": {
          "type": "string"
        },
        "kulpay_reference": {
          "type": "string"
        },
        "participants": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "role",
              "name"
            ],
            "additionalProperties": false,
            "properties": {
              "role": {
                "enum": [
                  "initiating_psp",
                  "from_psp",
                  "to_psp"
                ]
              },
              "psp_id": {
                "type": "string"
              },
              "account_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "method": {
                "type": "string"
              },
              "account_masked": {
                "type": "string"
              },
              "settlement": {
                "enum": [
                  "prefunded_balance",
                  "collected_from_payer",
                  "remittance",
                  "supplier_float",
                  "cash_deposit",
                  "kulpay_balance",
                  "wallet_payout",
                  "unroutable"
                ]
              }
            }
          },
          "minItems": 3,
          "description": "The PSPs in the payment and their roles: initiating_psp (the channel paid through), from_psp (where the money came from), to_psp (where it goes for the biller)."
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.pinned

A payer pinned (saved) the invoice (BEV-3): reported by the payer's channel, best effort; the payer is named by payer_ref only. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.pinned/v1",
  "title": "bill_invoice.pinned",
  "description": "A payer pinned (saved) the invoice (BEV-3): reported by the payer's channel, best effort; the payer is named by payer_ref only.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "channel",
        "payer_ref",
        "surface"
      ],
      "properties": {
        "channel": {
          "type": "object",
          "required": [
            "account_id",
            "name"
          ],
          "additionalProperties": false,
          "properties": {
            "account_id": {
              "type": "string"
            },
            "psp_id": {
              "type": "string"
            },
            "name": {
              "type": "string"
            }
          }
        },
        "payer_ref": {
          "type": "string",
          "pattern": "^pr_[0-9a-f]{24}$",
          "description": "An opaque id of the payer, stable per biller and payer; never their name, phone or email."
        },
        "surface": {
          "enum": [
            "app",
            "web",
            "ussd",
            "agent",
            "other"
          ]
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.presented

A biller presented an invoice, by API or file. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.presented/v2",
  "title": "bill_invoice.presented",
  "description": "A biller presented an invoice, by API or file. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "None: the invoice is payable on every channel."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "source"
      ],
      "properties": {
        "source": {
          "enum": [
            "push",
            "file"
          ]
        },
        "lines_count": {
          "type": "integer"
        },
        "has_document": {
          "type": "boolean"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.reminded

An invoice of one of your payers' subscriptions is due within three days and still open (BIL-5.6). Sent once per invoice. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.reminded/v1",
  "title": "bill_invoice.reminded",
  "description": "An invoice of one of your payers' subscriptions is due within three days and still open (BIL-5.6). Sent once per invoice.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Remind the payer."
  },
  "type": "object",
  "required": [
    "invoice_id",
    "biller",
    "number",
    "payer_id",
    "subscription_id",
    "amount_minor",
    "open_minor",
    "currency",
    "due_date"
  ],
  "properties": {
    "invoice_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "biller_name": {
      "type": "string"
    },
    "number": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "open_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "due_date": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_invoice.reminder_sent

A payer's channel was reminded of the invoice before its due date (BEV-1): {invoice, details}, sent to the biller as a webhook. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.reminder_sent/v1",
  "title": "bill_invoice.reminder_sent",
  "description": "A payer's channel was reminded of the invoice before its due date (BEV-1): {invoice, details}, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "channel",
        "payer_ref",
        "kind",
        "days_to_due"
      ],
      "properties": {
        "channel": {
          "type": "object",
          "required": [
            "account_id",
            "name"
          ],
          "additionalProperties": false,
          "properties": {
            "account_id": {
              "type": "string"
            },
            "psp_id": {
              "type": "string"
            },
            "name": {
              "type": "string"
            }
          }
        },
        "payer_ref": {
          "type": "string",
          "pattern": "^pr_[0-9a-f]{24}$",
          "description": "An opaque id of the payer, stable per biller and payer; never their name, phone or email."
        },
        "kind": {
          "enum": [
            "due_soon"
          ]
        },
        "days_to_due": {
          "type": "integer"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.remitted

Money paid on the invoice left KulPay in a remittance to the biller's bank (BEV-1): {invoice, details}, sent to the biller as a webhook. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.remitted/v1",
  "title": "bill_invoice.remitted",
  "description": "Money paid on the invoice left KulPay in a remittance to the biller's bank (BEV-1): {invoice, details}, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Match it to the remittance and its report."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "remittance_id",
        "bill_payment_id",
        "amount",
        "fee",
        "net"
      ],
      "properties": {
        "remittance_id": {
          "type": "string"
        },
        "bill_payment_id": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "fee": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "net": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.reopened

A payment of the invoice was reversed (F3-16): the invoice opens again (paid to open, or partially_paid while other payments remain) and can be paid again. When an earlier remittance already paid you that payment (remitted), the next remittance deducts it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.reopened/v1",
  "title": "bill_invoice.reopened",
  "description": "A payment of the invoice was reversed (F3-16): the invoice opens again (paid to open, or partially_paid while other payments remain) and can be paid again. When an earlier remittance already paid you that payment (remitted), the next remittance deducts it.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Show the invoice as unpaid again; expect the deduction in the next remittance when remitted is true."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "bill_payment_id",
        "reason",
        "previous_status",
        "reversed",
        "paid_total",
        "open",
        "remitted"
      ],
      "properties": {
        "bill_payment_id": {
          "type": "string"
        },
        "reason": {
          "type": "string"
        },
        "previous_status": {
          "enum": [
            "paid",
            "partially_paid"
          ]
        },
        "reversed": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid_total": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "remitted": {
          "type": "boolean"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.sent

The invoice reached a payer's channel: a subscriber's app, wallet or agent shows it (BEV-1): {invoice, details}, sent to the biller as a webhook. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.sent/v1",
  "title": "bill_invoice.sent",
  "description": "The invoice reached a payer's channel: a subscriber's app, wallet or agent shows it (BEV-1): {invoice, details}, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "channel",
        "payer_ref",
        "origin"
      ],
      "properties": {
        "channel": {
          "type": "object",
          "required": [
            "account_id",
            "name"
          ],
          "additionalProperties": false,
          "properties": {
            "account_id": {
              "type": "string"
            },
            "psp_id": {
              "type": "string"
            },
            "name": {
              "type": "string"
            }
          }
        },
        "payer_ref": {
          "type": "string",
          "pattern": "^pr_[0-9a-f]{24}$",
          "description": "An opaque id of the payer, stable per biller and payer; never their name, phone or email."
        },
        "origin": {
          "enum": [
            "payer",
            "biller_enrolled"
          ]
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.unpinned

A payer unpinned the invoice (BEV-3): reported by the payer's channel, best effort; the payer is named by payer_ref only. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.unpinned/v1",
  "title": "bill_invoice.unpinned",
  "description": "A payer unpinned the invoice (BEV-3): reported by the payer's channel, best effort; the payer is named by payer_ref only.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "channel",
        "payer_ref",
        "surface"
      ],
      "properties": {
        "channel": {
          "type": "object",
          "required": [
            "account_id",
            "name"
          ],
          "additionalProperties": false,
          "properties": {
            "account_id": {
              "type": "string"
            },
            "psp_id": {
              "type": "string"
            },
            "name": {
              "type": "string"
            }
          }
        },
        "payer_ref": {
          "type": "string",
          "pattern": "^pr_[0-9a-f]{24}$",
          "description": "An opaque id of the payer, stable per biller and payer; never their name, phone or email."
        },
        "surface": {
          "enum": [
            "app",
            "web",
            "ussd",
            "agent",
            "other"
          ]
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

bill_invoice.updated

An unpaid invoice changed: presented again, or its amount or due date patched. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.updated/v2",
  "title": "bill_invoice.updated",
  "description": "An unpaid invoice changed: presented again, or its amount or due date patched. v2 (BEV-1): {invoice, details}, decimal amounts, sent to the biller as a webhook.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Update your copy of the invoice."
  },
  "type": "object",
  "required": [
    "invoice",
    "details"
  ],
  "properties": {
    "invoice": {
      "type": "object",
      "description": "The invoice as it stands after the event: the same in every invoice event.",
      "required": [
        "id",
        "biller",
        "number",
        "account_reference",
        "amount",
        "paid",
        "open",
        "currency",
        "status",
        "due_date"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "biller": {
          "type": "string"
        },
        "number": {
          "type": "string"
        },
        "account_reference": {
          "type": "string"
        },
        "amount": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "paid": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "open": {
          "type": "string",
          "pattern": "^-?\\d+(\\.\\d+)?$"
        },
        "currency": {
          "type": "string"
        },
        "status": {
          "enum": [
            "open",
            "partially_paid",
            "paid",
            "cancelled",
            "expired"
          ]
        },
        "issue_date": {
          "type": "string"
        },
        "due_date": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "required": [
        "changes"
      ],
      "properties": {
        "changes": {
          "type": "object",
          "additionalProperties": {
            "type": "object",
            "properties": {
              "from": {},
              "to": {}
            }
          }
        },
        "via": {
          "type": "string"
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

biller events

EventWebhookTerminalFires whenWhat to do
bill_invoice.file_loaded v1 no no A biller's invoice file was loaded (BIL-5.3): checked as a whole, then row by row; each row created, updated or rejected an invoice. Loading the same file again updates rather than duplicates. Read the result file for rejected rows.
bill_recon.biller_report v1 no no A biller's daily collections report was reconciled with its bill payments of the day (F3-12): reported is what the report listed, kulpay_count what KulPay has, matched and breaks the result; the breaks are listed on GET /v1/biller/reports/daily and in the portal. Look at the breaks.
biller.created v2 no no An operator registered a biller (BIL-1.1). It starts as a draft: channels see it once a second operator activates it. A biller onboarded in KulOffice is created when KulOffice approves it, with its organisation (an LSP in technical setup) and technical status sandbox (IO-3.4). Configure its products, then request its activation.
biller.engagement_digest v1 yes no Yesterday in numbers, for a biller that chose the daily digest (F3-18): how many of its invoices were sent to payers channels, reminded, opened, pinned and paid on that day (Africa/Maputo). It replaces the webhooks of bill_invoice.sent, .reminder_sent, .opened, .pinned and .unpinned, which stay in the invoice timeline. Update your engagement reports; open the invoice timeline for detail.
biller.engagement_digest_set v1 no no A biller chose one daily engagement digest instead of a webhook per engagement event, or went back (F3-18). None.
biller.integration_status v1 yes no The technical status of a biller onboarded in KulOffice changed at KulPay (IO-3.5), told to KulOffice on its account: sandbox when KulOffice's approval created it, certified when its sandbox integration passed its certification, live when two operators put it live, incident when two operators suspended it for a technical incident. status is its effective status: active only when approved in KulOffice and live here (IO-3.6). KulOffice shows the integration status on the biller's record.
biller.notices_updated v1 no no A biller switched the invoice notices KulPay sends its payers on or off, or changed their wording (F3-15). None.
biller.status_changed v3 no no A biller was activated, suspended or retired (BIL-1.1): with the approval of a second operator (basis approval, approval_id set), or, for a sandbox biller whose products sell only through test-mode supply connectors, by the sandbox seed command acting as a system actor (basis sandbox_seed, F1-10). A suspended biller refuses new payments; payments in flight complete. A biller onboarded in KulOffice is active only when KulOffice has it approved and KulPay has it live (IO-3.6): basis origin when KulOffice's event changed it (suspended, reactivated, terminated); basis approval when KulPay's operators put it live or suspended it for an incident (its technical_status). Channels refresh their biller listing.
biller.updated v2 no no A biller's details changed (BIL-1.1): name, category, logo, support contact or linked organisation. A biller onboarded in KulOffice is updated by each of KulOffice's events, with its full commercial snapshot (IO-3.3). Channels refresh their biller listing.

bill_invoice.file_loaded

A biller's invoice file was loaded (BIL-5.3): checked as a whole, then row by row; each row created, updated or rejected an invoice. Loading the same file again updates rather than duplicates. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.file_loaded/v1",
  "title": "bill_invoice.file_loaded",
  "description": "A biller's invoice file was loaded (BIL-5.3): checked as a whole, then row by row; each row created, updated or rejected an invoice. Loading the same file again updates rather than duplicates.",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": false,
    "terminal": false,
    "act": "Read the result file for rejected rows."
  },
  "type": "object",
  "required": [
    "biller",
    "sha256",
    "rows",
    "created",
    "updated",
    "rejected"
  ],
  "properties": {
    "biller": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "rows": {
      "type": "integer"
    },
    "created": {
      "type": "integer"
    },
    "updated": {
      "type": "integer"
    },
    "rejected": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

bill_recon.biller_report

A biller's daily collections report was reconciled with its bill payments of the day (F3-12): reported is what the report listed, kulpay_count what KulPay has, matched and breaks the result; the breaks are listed on GET /v1/biller/reports/daily and in the portal. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_recon.biller_report/v1",
  "title": "bill_recon.biller_report",
  "description": "A biller's daily collections report was reconciled with its bill payments of the day (F3-12): reported is what the report listed, kulpay_count what KulPay has, matched and breaks the result; the breaks are listed on GET /v1/biller/reports/daily and in the portal.",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": false,
    "terminal": false,
    "act": "Look at the breaks."
  },
  "type": "object",
  "required": [
    "run_id",
    "biller",
    "day",
    "reported",
    "kulpay_count",
    "matched",
    "breaks"
  ],
  "properties": {
    "run_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "day": {
      "type": "string",
      "format": "date"
    },
    "reported": {
      "type": "integer"
    },
    "kulpay_count": {
      "type": "integer"
    },
    "matched": {
      "type": "integer"
    },
    "breaks": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

biller.created

An operator registered a biller (BIL-1.1). It starts as a draft: channels see it once a second operator activates it. A biller onboarded in KulOffice is created when KulOffice approves it, with its organisation (an LSP in technical setup) and technical status sandbox (IO-3.4). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.created/v2",
  "title": "biller.created",
  "description": "An operator registered a biller (BIL-1.1). It starts as a draft: channels see it once a second operator activates it. A biller onboarded in KulOffice is created when KulOffice approves it, with its organisation (an LSP in technical setup) and technical status sandbox (IO-3.4).",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": false,
    "terminal": false,
    "act": "Configure its products, then request its activation."
  },
  "type": "object",
  "required": [
    "biller_id",
    "code",
    "name",
    "category",
    "country",
    "status"
  ],
  "properties": {
    "biller_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "category": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "status": {
      "enum": [
        "draft",
        "active",
        "suspended",
        "retired"
      ]
    },
    "origin": {
      "enum": [
        "kuloffice"
      ],
      "description": "Where the biller's commercial record lives, when not at KulPay (IO-3.3): its commercial fields are read only here."
    },
    "external_id": {
      "type": "string",
      "description": "The biller's ID at its origin."
    },
    "commercial_status": {
      "type": "string",
      "description": "The biller's status at its origin (approved, suspended, terminated...)."
    },
    "technical_status": {
      "enum": [
        "sandbox",
        "certified",
        "live",
        "incident"
      ],
      "description": "KulPay's technical status of a biller onboarded elsewhere (IO-3.5); it is active only when approved there and live here (IO-3.6)."
    }
  },
  "additionalProperties": false
}

biller.engagement_digest

Yesterday in numbers, for a biller that chose the daily digest (F3-18): how many of its invoices were sent to payers channels, reminded, opened, pinned and paid on that day (Africa/Maputo). It replaces the webhooks of bill_invoice.sent, .reminder_sent, .opened, .pinned and .unpinned, which stay in the invoice timeline. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.engagement_digest/v1",
  "title": "biller.engagement_digest",
  "description": "Yesterday in numbers, for a biller that chose the daily digest (F3-18): how many of its invoices were sent to payers channels, reminded, opened, pinned and paid on that day (Africa/Maputo). It replaces the webhooks of bill_invoice.sent, .reminder_sent, .opened, .pinned and .unpinned, which stay in the invoice timeline.",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": true,
    "terminal": false,
    "act": "Update your engagement reports; open the invoice timeline for detail."
  },
  "type": "object",
  "required": [
    "biller",
    "date",
    "timezone",
    "invoices_sent",
    "invoices_reminded",
    "invoices_opened",
    "invoices_pinned",
    "invoices_paid"
  ],
  "properties": {
    "biller": {
      "type": "string"
    },
    "date": {
      "type": "string",
      "format": "date"
    },
    "timezone": {
      "type": "string"
    },
    "invoices_sent": {
      "type": "integer",
      "minimum": 0
    },
    "invoices_reminded": {
      "type": "integer",
      "minimum": 0
    },
    "invoices_opened": {
      "type": "integer",
      "minimum": 0
    },
    "invoices_pinned": {
      "type": "integer",
      "minimum": 0
    },
    "invoices_paid": {
      "type": "integer",
      "minimum": 0
    }
  },
  "additionalProperties": false
}

biller.engagement_digest_set

A biller chose one daily engagement digest instead of a webhook per engagement event, or went back (F3-18). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.engagement_digest_set/v1",
  "title": "biller.engagement_digest_set",
  "description": "A biller chose one daily engagement digest instead of a webhook per engagement event, or went back (F3-18).",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "biller_id",
    "code",
    "enabled"
  ],
  "properties": {
    "biller_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "enabled": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

biller.integration_status

The technical status of a biller onboarded in KulOffice changed at KulPay (IO-3.5), told to KulOffice on its account: sandbox when KulOffice's approval created it, certified when its sandbox integration passed its certification, live when two operators put it live, incident when two operators suspended it for a technical incident. status is its effective status: active only when approved in KulOffice and live here (IO-3.6). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.integration_status/v1",
  "title": "biller.integration_status",
  "description": "The technical status of a biller onboarded in KulOffice changed at KulPay (IO-3.5), told to KulOffice on its account: sandbox when KulOffice's approval created it, certified when its sandbox integration passed its certification, live when two operators put it live, incident when two operators suspended it for a technical incident. status is its effective status: active only when approved in KulOffice and live here (IO-3.6).",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": true,
    "terminal": false,
    "act": "KulOffice shows the integration status on the biller's record."
  },
  "type": "object",
  "required": [
    "biller_id",
    "code",
    "origin",
    "external_id",
    "technical_status",
    "status"
  ],
  "properties": {
    "biller_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "origin": {
      "enum": [
        "kuloffice"
      ]
    },
    "external_id": {
      "type": "string",
      "description": "The biller's ID in KulOffice."
    },
    "technical_status": {
      "enum": [
        "sandbox",
        "certified",
        "live",
        "incident"
      ]
    },
    "status": {
      "enum": [
        "draft",
        "active",
        "suspended",
        "retired"
      ]
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that put it live or suspended it."
    }
  },
  "additionalProperties": false
}

biller.notices_updated

A biller switched the invoice notices KulPay sends its payers on or off, or changed their wording (F3-15). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.notices_updated/v1",
  "title": "biller.notices_updated",
  "description": "A biller switched the invoice notices KulPay sends its payers on or off, or changed their wording (F3-15).",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "biller_id",
    "code",
    "notify_payers",
    "templates_count"
  ],
  "properties": {
    "biller_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "notify_payers": {
      "type": "boolean"
    },
    "templates_count": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

biller.status_changed

A biller was activated, suspended or retired (BIL-1.1): with the approval of a second operator (basis approval, approval_id set), or, for a sandbox biller whose products sell only through test-mode supply connectors, by the sandbox seed command acting as a system actor (basis sandbox_seed, F1-10). A suspended biller refuses new payments; payments in flight complete. A biller onboarded in KulOffice is active only when KulOffice has it approved and KulPay has it live (IO-3.6): basis origin when KulOffice's event changed it (suspended, reactivated, terminated); basis approval when KulPay's operators put it live or suspended it for an incident (its technical_status). Schema (JSON)

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.status_changed/v3",
  "title": "biller.status_changed",
  "description": "A biller was activated, suspended or retired (BIL-1.1): with the approval of a second operator (basis approval, approval_id set), or, for a sandbox biller whose products sell only through test-mode supply connectors, by the sandbox seed command acting as a system actor (basis sandbox_seed, F1-10). A suspended biller refuses new payments; payments in flight complete. A biller onboarded in KulOffice is active only when KulOffice has it approved and KulPay has it live (IO-3.6): basis origin when KulOffice's event changed it (suspended, reactivated, terminated); basis approval when KulPay's operators put it live or suspended it for an incident (its technical_status).",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": false,
    "terminal": false,
    "act": "Channels refresh their biller listing."
  },
  "type": "object",
  "required": [
    "biller_id",
    "code",
    "name",
    "category",
    "country",
    "status",
    "from",
    "basis"
  ],
  "properties": {
    "biller_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "category": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "status": {
      "enum": [
        "draft",
        "active",
        "suspended",
        "retired"
      ]
    },
    "from": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    },
    "basis": {
      "enum": [
        "approval",
        "sandbox_seed",
        "origin"
      ]
    },
    "origin": {
      "enum": [
        "kuloffice"
      ],
      "description": "Where the biller's commercial record lives, when not at KulPay (IO-3.3): its commercial fields are read only here."
    },
    "external_id": {
      "type": "string",
      "description": "The biller's ID at its origin."
    },
    "commercial_status": {
      "type": "string",
      "description": "The biller's status at its origin (approved, suspended, terminated...)."
    },
    "technical_status": {
      "enum": [
        "sandbox",
        "certified",
        "live",
        "incident"
      ],
      "description": "KulPay's technical status of a biller onboarded elsewhere (IO-3.5); it is active only when approved there and live here (IO-3.6)."
    }
  },
  "additionalProperties": false
}

biller.updated

A biller's details changed (BIL-1.1): name, category, logo, support contact or linked organisation. A biller onboarded in KulOffice is updated by each of KulOffice's events, with its full commercial snapshot (IO-3.3). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.updated/v2",
  "title": "biller.updated",
  "description": "A biller's details changed (BIL-1.1): name, category, logo, support contact or linked organisation. A biller onboarded in KulOffice is updated by each of KulOffice's events, with its full commercial snapshot (IO-3.3).",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": false,
    "terminal": false,
    "act": "Channels refresh their biller listing."
  },
  "type": "object",
  "required": [
    "biller_id",
    "code",
    "name",
    "category",
    "country",
    "status"
  ],
  "properties": {
    "biller_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "category": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "status": {
      "enum": [
        "draft",
        "active",
        "suspended",
        "retired"
      ]
    },
    "origin": {
      "enum": [
        "kuloffice"
      ],
      "description": "Where the biller's commercial record lives, when not at KulPay (IO-3.3): its commercial fields are read only here."
    },
    "external_id": {
      "type": "string",
      "description": "The biller's ID at its origin."
    },
    "commercial_status": {
      "type": "string",
      "description": "The biller's status at its origin (approved, suspended, terminated...)."
    },
    "technical_status": {
      "enum": [
        "sandbox",
        "certified",
        "live",
        "incident"
      ],
      "description": "KulPay's technical status of a biller onboarded elsewhere (IO-3.5); it is active only when approved there and live here (IO-3.6)."
    }
  },
  "additionalProperties": false
}

bill_lookup events

EventWebhookTerminalFires whenWhat to do
bill_lookup.completed v1 no no A channel looked a customer reference up at a biller's supplier (BIL-2.2). The reference is masked and the customer's name is never in the event. None.
bill_lookup.name_revealed v1 no no A bill lookup gave a channel the customer full name, as its contract allows (F3-17). The audit trail keeps the name masked. None.

bill_lookup.completed

A channel looked a customer reference up at a biller's supplier (BIL-2.2). The reference is masked and the customer's name is never in the event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_lookup.completed/v1",
  "title": "bill_lookup.completed",
  "description": "A channel looked a customer reference up at a biller's supplier (BIL-2.2). The reference is masked and the customer's name is never in the event.",
  "x-kulpay": {
    "aggregate": "bill_lookup",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "lookup_id",
    "biller",
    "product",
    "status"
  ],
  "properties": {
    "lookup_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "product": {
      "type": "string"
    },
    "status": {
      "enum": [
        "found",
        "not_found"
      ]
    },
    "reference_masked": {
      "type": "string"
    },
    "connector": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_lookup.name_revealed

A bill lookup gave a channel the customer full name, as its contract allows (F3-17). The audit trail keeps the name masked. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_lookup.name_revealed/v1",
  "title": "bill_lookup.name_revealed",
  "description": "A bill lookup gave a channel the customer full name, as its contract allows (F3-17). The audit trail keeps the name masked.",
  "x-kulpay": {
    "aggregate": "bill_lookup",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "lookup_id",
    "channel_id",
    "biller",
    "product",
    "masked_name",
    "contract_reference"
  ],
  "properties": {
    "lookup_id": {
      "type": "string"
    },
    "channel_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "product": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "masked_name": {
      "type": "string"
    },
    "contract_reference": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_enrolment events

EventWebhookTerminalFires whenWhat to do
bill_payer.enrolled v1 no no A biller enrolled a customer to receive its bills on KulPay channels (BIL-5.5), declaring its lawful basis. Their contact is sealed and never in the event. None.
bill_payer.notices_stopped v1 no no A payer stopped the invoice notices KulPay sends them (F3-15): STOP by SMS or the email link. Nothing more is sent to them for that enrolment, and the biller enrolling them again does not undo it. None: the biller sees it on the enrolment.
bill_payer.unenrolled v1 no no A biller removed an enrolment (BIL-5.5): the subscriptions it created end, and its data is deleted after 30 days. None.

bill_payer.enrolled

A biller enrolled a customer to receive its bills on KulPay channels (BIL-5.5), declaring its lawful basis. Their contact is sealed and never in the event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payer.enrolled/v1",
  "title": "bill_payer.enrolled",
  "description": "A biller enrolled a customer to receive its bills on KulPay channels (BIL-5.5), declaring its lawful basis. Their contact is sealed and never in the event.",
  "x-kulpay": {
    "aggregate": "bill_enrolment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "enrolment_id",
    "biller",
    "lawful_basis"
  ],
  "properties": {
    "enrolment_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "account_reference_masked": {
      "type": "string"
    },
    "lawful_basis": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payer.notices_stopped

A payer stopped the invoice notices KulPay sends them (F3-15): STOP by SMS or the email link. Nothing more is sent to them for that enrolment, and the biller enrolling them again does not undo it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payer.notices_stopped/v1",
  "title": "bill_payer.notices_stopped",
  "description": "A payer stopped the invoice notices KulPay sends them (F3-15): STOP by SMS or the email link. Nothing more is sent to them for that enrolment, and the biller enrolling them again does not undo it.",
  "x-kulpay": {
    "aggregate": "bill_enrolment",
    "webhook": false,
    "terminal": false,
    "act": "None: the biller sees it on the enrolment."
  },
  "type": "object",
  "required": [
    "enrolment_id",
    "biller",
    "via",
    "stopped_at"
  ],
  "properties": {
    "enrolment_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "account_reference_masked": {
      "type": "string"
    },
    "via": {
      "enum": [
        "sms",
        "email"
      ]
    },
    "stopped_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

bill_payer.unenrolled

A biller removed an enrolment (BIL-5.5): the subscriptions it created end, and its data is deleted after 30 days. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payer.unenrolled/v1",
  "title": "bill_payer.unenrolled",
  "description": "A biller removed an enrolment (BIL-5.5): the subscriptions it created end, and its data is deleted after 30 days.",
  "x-kulpay": {
    "aggregate": "bill_enrolment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "enrolment_id",
    "biller",
    "lawful_basis"
  ],
  "properties": {
    "enrolment_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "account_reference_masked": {
      "type": "string"
    },
    "lawful_basis": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_payer events

EventWebhookTerminalFires whenWhat to do
bill_payer.saved v1 no no A channel created or updated a payer in KulPay's payer directory (BIL-5.4). Their contact is sealed and never in the event. None.

bill_payer.saved

A channel created or updated a payer in KulPay's payer directory (BIL-5.4). Their contact is sealed and never in the event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payer.saved/v1",
  "title": "bill_payer.saved",
  "description": "A channel created or updated a payer in KulPay's payer directory (BIL-5.4). Their contact is sealed and never in the event.",
  "x-kulpay": {
    "aggregate": "bill_payer",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "payer_id",
    "external_id",
    "created"
  ],
  "properties": {
    "payer_id": {
      "type": "string"
    },
    "external_id": {
      "type": "string"
    },
    "created": {
      "type": "boolean"
    },
    "has_phone": {
      "type": "boolean"
    },
    "has_email": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

bill_product events

EventWebhookTerminalFires whenWhat to do
bill_product.created v1 no no An operator configured a new product for a biller (BIL-1.2): its shape, amount rule, supply connectors, commercial model and fee bearer. None.
bill_product.updated v1 no no A product's configuration changed (BIL-1.2, BIL-1.3): a new version, its channels or an offer. Payments already quoted keep the version they were quoted under. Channels refresh their biller listing.

bill_product.created

An operator configured a new product for a biller (BIL-1.2): its shape, amount rule, supply connectors, commercial model and fee bearer. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_product.created/v1",
  "title": "bill_product.created",
  "description": "An operator configured a new product for a biller (BIL-1.2): its shape, amount rule, supply connectors, commercial model and fee bearer.",
  "x-kulpay": {
    "aggregate": "bill_product",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "product_id",
    "biller_id",
    "code",
    "shape",
    "model",
    "fee_bearer",
    "version",
    "enabled"
  ],
  "properties": {
    "product_id": {
      "type": "string"
    },
    "biller_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "shape": {
      "type": "string"
    },
    "model": {
      "enum": [
        "principal",
        "agent"
      ]
    },
    "fee_bearer": {
      "enum": [
        "payer",
        "biller"
      ]
    },
    "version": {
      "type": "integer"
    },
    "enabled": {
      "type": "boolean"
    },
    "channels": {
      "type": "integer"
    },
    "offer": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_product.updated

A product's configuration changed (BIL-1.2, BIL-1.3): a new version, its channels or an offer. Payments already quoted keep the version they were quoted under. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_product.updated/v1",
  "title": "bill_product.updated",
  "description": "A product's configuration changed (BIL-1.2, BIL-1.3): a new version, its channels or an offer. Payments already quoted keep the version they were quoted under.",
  "x-kulpay": {
    "aggregate": "bill_product",
    "webhook": false,
    "terminal": false,
    "act": "Channels refresh their biller listing."
  },
  "type": "object",
  "required": [
    "product_id",
    "biller_id",
    "code",
    "shape",
    "model",
    "fee_bearer",
    "version",
    "enabled"
  ],
  "properties": {
    "product_id": {
      "type": "string"
    },
    "biller_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "shape": {
      "type": "string"
    },
    "model": {
      "enum": [
        "principal",
        "agent"
      ]
    },
    "fee_bearer": {
      "enum": [
        "payer",
        "biller"
      ]
    },
    "version": {
      "type": "integer"
    },
    "enabled": {
      "type": "boolean"
    },
    "channels": {
      "type": "integer"
    },
    "offer": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_remittance events

EventWebhookTerminalFires whenWhat to do
bill_remittance.created v2 yes no A remittance to a biller was created (BIL-5.7): the agent-model payments KulPay collected for it, less the fees it bears. Its report lists every payment. v2 (IO-4.3): rail, the way it goes to the biller's credit account. Expect the transfer; reconcile with the report.
bill_remittance.paid v4 yes yes KulPay paid a remittance to the biller (BIL-5.7). paid_by says how: bank_rail, a transfer in the bank rail's batch, debited by the bank (bank_reference is its EndToEndId, F2-4); manual, a transfer treasury made by hand and recorded, approved by a second person; simulation, a test-mode remittance paid as soon as it is created (simulated: true, bank_reference SIMULATED-<id>, F1-13). v4 (IO-4.3): rail, the way it goes to the biller's credit account. Match the transfer with its bank_reference.
bill_remittance.paying v2 no no A second person approved paying a remittance by the bank rail: its transfer to the biller's settlement account is in the next batch to the sponsor bank (F2-4). bill_remittance.paid follows when the bank debits it, or bill_remittance.payment_failed if the bank rejects it. v2 (IO-4.3): rail, the way it goes to the biller's credit account. Nothing: paid or payment_failed follows.
bill_remittance.payment_failed v2 no no The bank rejected or returned a remittance's transfer (F2-4): the money is back in client funds and the remittance is pending again, to be paid again by the rail or recorded by hand. A remittance already paid is reopened and its payable restored. v2 (IO-4.3): rail, the way it goes to the biller's credit account. Check the biller's settlement account, then pay the remittance again.

bill_remittance.created

A remittance to a biller was created (BIL-5.7): the agent-model payments KulPay collected for it, less the fees it bears. Its report lists every payment. v2 (IO-4.3): rail, the way it goes to the biller's credit account. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_remittance.created/v2",
  "title": "bill_remittance.created",
  "description": "A remittance to a biller was created (BIL-5.7): the agent-model payments KulPay collected for it, less the fees it bears. Its report lists every payment. v2 (IO-4.3): rail, the way it goes to the biller's credit account.",
  "x-kulpay": {
    "aggregate": "bill_remittance",
    "webhook": true,
    "terminal": false,
    "act": "Expect the transfer; reconcile with the report."
  },
  "type": "object",
  "required": [
    "remittance_id",
    "biller",
    "currency",
    "count",
    "gross_minor",
    "fee_minor",
    "net_minor",
    "status"
  ],
  "properties": {
    "remittance_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "gross_minor": {
      "type": "integer"
    },
    "fee_minor": {
      "type": "integer"
    },
    "net_minor": {
      "type": "integer"
    },
    "period_from": {
      "type": "string",
      "format": "date-time"
    },
    "period_to": {
      "type": "string",
      "format": "date-time"
    },
    "status": {
      "enum": [
        "pending",
        "paid"
      ]
    },
    "rail": {
      "enum": [
        "internal_transfer",
        "bank_rail",
        "wallet_payout",
        "kulpay_balance"
      ],
      "description": "How the remittance goes to the biller (IO-4.3): within KulPay's bank, by the bank rail, by its wallet operator's payout, or to its own KulPay balance."
    }
  },
  "additionalProperties": false
}

bill_remittance.paid

KulPay paid a remittance to the biller (BIL-5.7). paid_by says how: bank_rail, a transfer in the bank rail's batch, debited by the bank (bank_reference is its EndToEndId, F2-4); manual, a transfer treasury made by hand and recorded, approved by a second person; simulation, a test-mode remittance paid as soon as it is created (simulated: true, bank_reference SIMULATED-<id>, F1-13). v4 (IO-4.3): rail, the way it goes to the biller's credit account. Schema (JSON)

Schema v4
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_remittance.paid/v4",
  "title": "bill_remittance.paid",
  "description": "KulPay paid a remittance to the biller (BIL-5.7). paid_by says how: bank_rail, a transfer in the bank rail's batch, debited by the bank (bank_reference is its EndToEndId, F2-4); manual, a transfer treasury made by hand and recorded, approved by a second person; simulation, a test-mode remittance paid as soon as it is created (simulated: true, bank_reference SIMULATED-<id>, F1-13). v4 (IO-4.3): rail, the way it goes to the biller's credit account.",
  "x-kulpay": {
    "aggregate": "bill_remittance",
    "webhook": true,
    "terminal": true,
    "act": "Match the transfer with its bank_reference."
  },
  "type": "object",
  "required": [
    "remittance_id",
    "biller",
    "currency",
    "count",
    "gross_minor",
    "fee_minor",
    "net_minor",
    "status",
    "bank_reference",
    "paid_by"
  ],
  "properties": {
    "remittance_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "gross_minor": {
      "type": "integer"
    },
    "fee_minor": {
      "type": "integer"
    },
    "net_minor": {
      "type": "integer"
    },
    "period_from": {
      "type": "string",
      "format": "date-time"
    },
    "period_to": {
      "type": "string",
      "format": "date-time"
    },
    "status": {
      "enum": [
        "paid"
      ]
    },
    "bank_reference": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    },
    "simulated": {
      "type": "boolean"
    },
    "paid_by": {
      "enum": [
        "bank_rail",
        "manual",
        "simulation",
        "kulpay_balance",
        "wallet_payout"
      ]
    },
    "payout_id": {
      "type": "string"
    },
    "rail": {
      "enum": [
        "internal_transfer",
        "bank_rail",
        "wallet_payout",
        "kulpay_balance"
      ],
      "description": "How the remittance goes to the biller (IO-4.3): within KulPay's bank, by the bank rail, by its wallet operator's payout, or to its own KulPay balance."
    }
  },
  "additionalProperties": false
}

bill_remittance.paying

A second person approved paying a remittance by the bank rail: its transfer to the biller's settlement account is in the next batch to the sponsor bank (F2-4). bill_remittance.paid follows when the bank debits it, or bill_remittance.payment_failed if the bank rejects it. v2 (IO-4.3): rail, the way it goes to the biller's credit account. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_remittance.paying/v2",
  "title": "bill_remittance.paying",
  "description": "A second person approved paying a remittance by the bank rail: its transfer to the biller's settlement account is in the next batch to the sponsor bank (F2-4). bill_remittance.paid follows when the bank debits it, or bill_remittance.payment_failed if the bank rejects it. v2 (IO-4.3): rail, the way it goes to the biller's credit account.",
  "x-kulpay": {
    "aggregate": "bill_remittance",
    "webhook": false,
    "terminal": false,
    "act": "Nothing: paid or payment_failed follows."
  },
  "type": "object",
  "required": [
    "remittance_id",
    "biller",
    "currency",
    "count",
    "gross_minor",
    "fee_minor",
    "net_minor",
    "status",
    "payout_id",
    "end_to_end_id"
  ],
  "properties": {
    "remittance_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "gross_minor": {
      "type": "integer"
    },
    "fee_minor": {
      "type": "integer"
    },
    "net_minor": {
      "type": "integer"
    },
    "period_from": {
      "type": "string",
      "format": "date-time"
    },
    "period_to": {
      "type": "string",
      "format": "date-time"
    },
    "status": {
      "enum": [
        "paying"
      ]
    },
    "approval_id": {
      "type": "string"
    },
    "payout_id": {
      "type": "string"
    },
    "end_to_end_id": {
      "type": "string"
    },
    "rail": {
      "enum": [
        "internal_transfer",
        "bank_rail",
        "wallet_payout",
        "kulpay_balance"
      ],
      "description": "How the remittance goes to the biller (IO-4.3): within KulPay's bank, by the bank rail, by its wallet operator's payout, or to its own KulPay balance."
    }
  },
  "additionalProperties": false
}

bill_remittance.payment_failed

The bank rejected or returned a remittance's transfer (F2-4): the money is back in client funds and the remittance is pending again, to be paid again by the rail or recorded by hand. A remittance already paid is reopened and its payable restored. v2 (IO-4.3): rail, the way it goes to the biller's credit account. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_remittance.payment_failed/v2",
  "title": "bill_remittance.payment_failed",
  "description": "The bank rejected or returned a remittance's transfer (F2-4): the money is back in client funds and the remittance is pending again, to be paid again by the rail or recorded by hand. A remittance already paid is reopened and its payable restored. v2 (IO-4.3): rail, the way it goes to the biller's credit account.",
  "x-kulpay": {
    "aggregate": "bill_remittance",
    "webhook": false,
    "terminal": false,
    "act": "Check the biller's settlement account, then pay the remittance again."
  },
  "type": "object",
  "required": [
    "remittance_id",
    "biller",
    "currency",
    "count",
    "gross_minor",
    "fee_minor",
    "net_minor",
    "status",
    "payout_id",
    "reason"
  ],
  "properties": {
    "remittance_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "gross_minor": {
      "type": "integer"
    },
    "fee_minor": {
      "type": "integer"
    },
    "net_minor": {
      "type": "integer"
    },
    "period_from": {
      "type": "string",
      "format": "date-time"
    },
    "period_to": {
      "type": "string",
      "format": "date-time"
    },
    "status": {
      "enum": [
        "pending"
      ]
    },
    "payout_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "rail": {
      "enum": [
        "internal_transfer",
        "bank_rail",
        "wallet_payout",
        "kulpay_balance"
      ],
      "description": "How the remittance goes to the biller (IO-4.3): within KulPay's bank, by the bank rail, by its wallet operator's payout, or to its own KulPay balance."
    }
  },
  "additionalProperties": false
}

bill_subscription events

EventWebhookTerminalFires whenWhat to do
bill_subscription.ended v1 yes yes The biller removed the enrolment a subscription came from (BIL-5.5); its bills are no longer delivered. None.
bill_subscription.opted_in v1 yes no A payer now receives a biller account's bills through your channel (BIL-5.4, BIL-5.5): they opted in with their consent (origin payer), or the biller enrolled them (origin biller_enrolled, consent_version biller:<lawful basis>). When the biller enrolled them, tell the payer, with a way to opt out.
bill_subscription.opted_out v1 yes yes A payer withdrew their subscription to a biller account (BIL-5.4): nothing of it is delivered any more, and a later enrolment by the biller does not subscribe them again. None.

bill_subscription.ended

The biller removed the enrolment a subscription came from (BIL-5.5); its bills are no longer delivered. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_subscription.ended/v1",
  "title": "bill_subscription.ended",
  "description": "The biller removed the enrolment a subscription came from (BIL-5.5); its bills are no longer delivered.",
  "x-kulpay": {
    "aggregate": "bill_subscription",
    "webhook": true,
    "terminal": true,
    "act": "None."
  },
  "type": "object",
  "required": [
    "subscription_id",
    "payer_id",
    "external_id",
    "biller",
    "status",
    "origin",
    "consent_version"
  ],
  "properties": {
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "external_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "account_reference_masked": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "pending_verification",
        "opted_out",
        "ended"
      ]
    },
    "origin": {
      "enum": [
        "payer",
        "biller_enrolled"
      ]
    },
    "consent_version": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_subscription.opted_in

A payer now receives a biller account's bills through your channel (BIL-5.4, BIL-5.5): they opted in with their consent (origin payer), or the biller enrolled them (origin biller_enrolled, consent_version biller:<lawful basis>). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_subscription.opted_in/v1",
  "title": "bill_subscription.opted_in",
  "description": "A payer now receives a biller account's bills through your channel (BIL-5.4, BIL-5.5): they opted in with their consent (origin payer), or the biller enrolled them (origin biller_enrolled, consent_version biller:<lawful basis>).",
  "x-kulpay": {
    "aggregate": "bill_subscription",
    "webhook": true,
    "terminal": false,
    "act": "When the biller enrolled them, tell the payer, with a way to opt out."
  },
  "type": "object",
  "required": [
    "subscription_id",
    "payer_id",
    "external_id",
    "biller",
    "status",
    "origin",
    "consent_version"
  ],
  "properties": {
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "external_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "account_reference_masked": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "pending_verification",
        "opted_out",
        "ended"
      ]
    },
    "origin": {
      "enum": [
        "payer",
        "biller_enrolled"
      ]
    },
    "consent_version": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_subscription.opted_out

A payer withdrew their subscription to a biller account (BIL-5.4): nothing of it is delivered any more, and a later enrolment by the biller does not subscribe them again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_subscription.opted_out/v1",
  "title": "bill_subscription.opted_out",
  "description": "A payer withdrew their subscription to a biller account (BIL-5.4): nothing of it is delivered any more, and a later enrolment by the biller does not subscribe them again.",
  "x-kulpay": {
    "aggregate": "bill_subscription",
    "webhook": true,
    "terminal": true,
    "act": "None."
  },
  "type": "object",
  "required": [
    "subscription_id",
    "payer_id",
    "external_id",
    "biller",
    "status",
    "origin",
    "consent_version"
  ],
  "properties": {
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "external_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "account_reference_masked": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "pending_verification",
        "opted_out",
        "ended"
      ]
    },
    "origin": {
      "enum": [
        "payer",
        "biller_enrolled"
      ]
    },
    "consent_version": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

callback events

EventWebhookTerminalFires whenWhat to do
callback.dead_lettered v1 no yes A verified callback failed every processing attempt, retried with exponential backoff, and moved to the dead-letter queue (PSP-7.4). The payment it reports has not moved. Find why it failed (the error names it: an unknown reference, a binding removed, a provider format change), fix the cause, then replay it from /admin/callbacks.
callback.duplicate v1 no no A PSP resent a callback KulPay already holds (PSP-7.4). It was answered 200, so the PSP stops resending, and not processed again. The key is the provider's own event id, else its transaction reference and status, a body hash only as a last resort; it is scoped to the PSP. None. Many in a row from one PSP mean it is not seeing KulPay's 200s.
callback.received v1 no no A PSP's callback arrived on a binding's own endpoint, /callbacks/{psp_id}/{binding_id} (PSP-7.3). Its raw request is stored; callback.verified or callback.rejected follows at once. None.
callback.rejected v1 no yes A callback failed its binding's verification and was answered 401 (PSP-7.3). The raw request is kept, never processed, for investigation. Investigate: a forged callback, a PSP calling from an address not on the allow-list, or a secret or certificate that changed on one side only.
callback.verified v1 no no A callback passed its binding's verification (the driver's signature under the tenant's credentials, an HMAC, a token or a pinned certificate, and the IP allow-list when set) and was queued for asynchronous processing (PSP-7.3). None.

callback.dead_lettered

A verified callback failed every processing attempt, retried with exponential backoff, and moved to the dead-letter queue (PSP-7.4). The payment it reports has not moved. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/callback.dead_lettered/v1",
  "title": "callback.dead_lettered",
  "description": "A verified callback failed every processing attempt, retried with exponential backoff, and moved to the dead-letter queue (PSP-7.4). The payment it reports has not moved.",
  "x-kulpay": {
    "aggregate": "callback",
    "webhook": false,
    "terminal": true,
    "act": "Find why it failed (the error names it: an unknown reference, a binding removed, a provider format change), fix the cause, then replay it from /admin/callbacks."
  },
  "type": "object",
  "required": [
    "callback_id",
    "provider",
    "attempts"
  ],
  "properties": {
    "callback_id": {
      "type": "string"
    },
    "provider": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

callback.duplicate

A PSP resent a callback KulPay already holds (PSP-7.4). It was answered 200, so the PSP stops resending, and not processed again. The key is the provider's own event id, else its transaction reference and status, a body hash only as a last resort; it is scoped to the PSP. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/callback.duplicate/v1",
  "title": "callback.duplicate",
  "description": "A PSP resent a callback KulPay already holds (PSP-7.4). It was answered 200, so the PSP stops resending, and not processed again. The key is the provider's own event id, else its transaction reference and status, a body hash only as a last resort; it is scoped to the PSP.",
  "x-kulpay": {
    "aggregate": "callback",
    "webhook": false,
    "terminal": false,
    "act": "None. Many in a row from one PSP mean it is not seeing KulPay's 200s."
  },
  "type": "object",
  "required": [
    "callback_id",
    "provider",
    "dedup_key",
    "status"
  ],
  "properties": {
    "callback_id": {
      "type": "string",
      "description": "The callback this one repeats."
    },
    "provider": {
      "type": "string"
    },
    "binding_id": {
      "type": "string"
    },
    "dedup_key": {
      "enum": [
        "provider_event_id",
        "reference_status",
        "body_hash"
      ]
    },
    "status": {
      "type": "string",
      "description": "Where the original callback is."
    }
  },
  "additionalProperties": false
}

callback.received

A PSP's callback arrived on a binding's own endpoint, /callbacks/{psp_id}/{binding_id} (PSP-7.3). Its raw request is stored; callback.verified or callback.rejected follows at once. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/callback.received/v1",
  "title": "callback.received",
  "description": "A PSP's callback arrived on a binding's own endpoint, /callbacks/{psp_id}/{binding_id} (PSP-7.3). Its raw request is stored; callback.verified or callback.rejected follows at once.",
  "x-kulpay": {
    "aggregate": "callback",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "callback_id",
    "provider",
    "binding_id",
    "method",
    "verification"
  ],
  "properties": {
    "callback_id": {
      "type": "string"
    },
    "provider": {
      "type": "string"
    },
    "binding_id": {
      "type": "string"
    },
    "method": {
      "type": "string"
    },
    "verification": {
      "enum": [
        "connector",
        "hmac",
        "token",
        "certificate",
        "none"
      ]
    },
    "remote_ip": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

callback.rejected

A callback failed its binding's verification and was answered 401 (PSP-7.3). The raw request is kept, never processed, for investigation. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/callback.rejected/v1",
  "title": "callback.rejected",
  "description": "A callback failed its binding's verification and was answered 401 (PSP-7.3). The raw request is kept, never processed, for investigation.",
  "x-kulpay": {
    "aggregate": "callback",
    "webhook": false,
    "terminal": true,
    "act": "Investigate: a forged callback, a PSP calling from an address not on the allow-list, or a secret or certificate that changed on one side only."
  },
  "type": "object",
  "required": [
    "callback_id",
    "provider",
    "binding_id",
    "method",
    "verification",
    "reason"
  ],
  "properties": {
    "callback_id": {
      "type": "string"
    },
    "provider": {
      "type": "string"
    },
    "binding_id": {
      "type": "string"
    },
    "method": {
      "type": "string"
    },
    "verification": {
      "enum": [
        "connector",
        "hmac",
        "token",
        "certificate",
        "none"
      ]
    },
    "remote_ip": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "ip_not_allowed",
        "invalid_signature",
        "invalid_token",
        "client_certificate_missing",
        "client_certificate_mismatch",
        "binding_secret_unavailable",
        "binding_unavailable",
        "binding_cannot_verify",
        "unverified_live_binding"
      ]
    }
  },
  "additionalProperties": false
}

callback.verified

A callback passed its binding's verification (the driver's signature under the tenant's credentials, an HMAC, a token or a pinned certificate, and the IP allow-list when set) and was queued for asynchronous processing (PSP-7.3). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/callback.verified/v1",
  "title": "callback.verified",
  "description": "A callback passed its binding's verification (the driver's signature under the tenant's credentials, an HMAC, a token or a pinned certificate, and the IP allow-list when set) and was queued for asynchronous processing (PSP-7.3).",
  "x-kulpay": {
    "aggregate": "callback",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "callback_id",
    "provider",
    "binding_id",
    "method",
    "verification"
  ],
  "properties": {
    "callback_id": {
      "type": "string"
    },
    "provider": {
      "type": "string"
    },
    "binding_id": {
      "type": "string"
    },
    "method": {
      "type": "string"
    },
    "verification": {
      "enum": [
        "connector",
        "hmac",
        "token",
        "certificate",
        "none"
      ]
    },
    "remote_ip": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

deposit_credit events

EventWebhookTerminalFires whenWhat to do
cash_in.application_applied v1 yes yes The resolver credited the wallet (IO-2.3, IO-2.4), with its transaction; once per deposit. The resolver notifies its customer. None for KulPay.
cash_in.application_pending v1 no no A confirmed deposit is to be applied to the wallet its resolver decided (IO-2.4): KulPay sends the credit, keyed by the deposit, until the resolver answers; a timeout is queried, never guessed. Also recorded when a person sends a credit in review again. None: the outcome follows as applied, rejected or review_required.
cash_in.application_rejected v1 yes yes The resolver refused the credit, with a reason (wallet_closed, limit_exceeded, customer_unknown), or a person decided in review it will not be applied (IO-2.4). The money stays with the deposit, for the queue of deposits that cannot be applied (IO-2.6). An operator assigns or returns the deposit (IO-2.6).
cash_in.application_retry_scheduled v1 no no An attempt to apply the credit did not conclude (IO-2.4): unknown (a timeout, a dropped connection or a 5xx: queried before it is sent again) or refused before anything was applied (sent again as it is). Records the next attempt. None: KulPay attempts again on schedule.
cash_in.application_review_required v1 no no The credit is unresolved after its window (24 h) or its resolver cannot apply credits (IO-2.4): a person decides, with four eyes, whether it was applied, is rejected or is sent again. An operator checks the resolver and resolves the credit with four eyes.
cash_in.credit_reversal_failed v1 no no The resolver refused to take the credit back, or did not answer within 72 hours (IO-2.7): a credit_reversal_failed exception waits for an operator. An operator works the exception.
cash_in.credit_reversal_requested v1 no no A second operator approved taking an applied credit back, the bank having returned the deposit (IO-2.7): KulPay asks the resolver to reverse it until it answers. None: KulPay sends the reversal.
cash_in.credit_reversal_retry_scheduled v1 no no A credit's reversal got no answer KulPay could read (IO-2.7): the same reversal is sent again, the resolver keying it by the credit. None: KulPay attempts again on schedule.
cash_in.credit_reversed v1 no no The resolver took the credit back (IO-2.7): it debited the customer or, short of balance, recorded the debt and held the wallet for withdrawals. The deposit is returned (cash_in.returned). None.

cash_in.application_applied

The resolver credited the wallet (IO-2.3, IO-2.4), with its transaction; once per deposit. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.application_applied/v1",
  "title": "cash_in.application_applied",
  "description": "The resolver credited the wallet (IO-2.3, IO-2.4), with its transaction; once per deposit.",
  "x-kulpay": {
    "aggregate": "deposit_credit",
    "webhook": true,
    "terminal": true,
    "act": "The resolver notifies its customer. None for KulPay."
  },
  "type": "object",
  "required": [
    "credit_id",
    "deposit_id",
    "intent_id",
    "lookup_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "status",
    "attempts",
    "transaction_id"
  ],
  "properties": {
    "credit_id": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "pending",
        "unknown",
        "applied",
        "rejected",
        "review_required"
      ]
    },
    "attempts": {
      "type": "integer",
      "minimum": 0
    },
    "transaction_id": {
      "type": "string",
      "description": "The resolver's transaction (the BLNK transaction at KulOffice)."
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that decided it, when a person did."
    }
  },
  "additionalProperties": false
}

cash_in.application_pending

A confirmed deposit is to be applied to the wallet its resolver decided (IO-2.4): KulPay sends the credit, keyed by the deposit, until the resolver answers; a timeout is queried, never guessed. Also recorded when a person sends a credit in review again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.application_pending/v1",
  "title": "cash_in.application_pending",
  "description": "A confirmed deposit is to be applied to the wallet its resolver decided (IO-2.4): KulPay sends the credit, keyed by the deposit, until the resolver answers; a timeout is queried, never guessed. Also recorded when a person sends a credit in review again.",
  "x-kulpay": {
    "aggregate": "deposit_credit",
    "webhook": false,
    "terminal": false,
    "act": "None: the outcome follows as applied, rejected or review_required."
  },
  "type": "object",
  "required": [
    "credit_id",
    "deposit_id",
    "intent_id",
    "lookup_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "status",
    "attempts"
  ],
  "properties": {
    "credit_id": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "pending",
        "unknown",
        "applied",
        "rejected",
        "review_required"
      ]
    },
    "attempts": {
      "type": "integer",
      "minimum": 0
    },
    "transaction_id": {
      "type": "string",
      "description": "The resolver's transaction (the BLNK transaction at KulOffice)."
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that decided it, when a person did."
    }
  },
  "additionalProperties": false
}

cash_in.application_rejected

The resolver refused the credit, with a reason (wallet_closed, limit_exceeded, customer_unknown), or a person decided in review it will not be applied (IO-2.4). The money stays with the deposit, for the queue of deposits that cannot be applied (IO-2.6). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.application_rejected/v1",
  "title": "cash_in.application_rejected",
  "description": "The resolver refused the credit, with a reason (wallet_closed, limit_exceeded, customer_unknown), or a person decided in review it will not be applied (IO-2.4). The money stays with the deposit, for the queue of deposits that cannot be applied (IO-2.6).",
  "x-kulpay": {
    "aggregate": "deposit_credit",
    "webhook": true,
    "terminal": true,
    "act": "An operator assigns or returns the deposit (IO-2.6)."
  },
  "type": "object",
  "required": [
    "credit_id",
    "deposit_id",
    "intent_id",
    "lookup_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "status",
    "attempts",
    "reason"
  ],
  "properties": {
    "credit_id": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "pending",
        "unknown",
        "applied",
        "rejected",
        "review_required"
      ]
    },
    "attempts": {
      "type": "integer",
      "minimum": 0
    },
    "transaction_id": {
      "type": "string",
      "description": "The resolver's transaction (the BLNK transaction at KulOffice)."
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that decided it, when a person did."
    }
  },
  "additionalProperties": false
}

cash_in.application_retry_scheduled

An attempt to apply the credit did not conclude (IO-2.4): unknown (a timeout, a dropped connection or a 5xx: queried before it is sent again) or refused before anything was applied (sent again as it is). Records the next attempt. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.application_retry_scheduled/v1",
  "title": "cash_in.application_retry_scheduled",
  "description": "An attempt to apply the credit did not conclude (IO-2.4): unknown (a timeout, a dropped connection or a 5xx: queried before it is sent again) or refused before anything was applied (sent again as it is). Records the next attempt.",
  "x-kulpay": {
    "aggregate": "deposit_credit",
    "webhook": false,
    "terminal": false,
    "act": "None: KulPay attempts again on schedule."
  },
  "type": "object",
  "required": [
    "credit_id",
    "deposit_id",
    "intent_id",
    "lookup_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "status",
    "attempts",
    "next_attempt_at",
    "error_class"
  ],
  "properties": {
    "credit_id": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "pending",
        "unknown",
        "applied",
        "rejected",
        "review_required"
      ]
    },
    "attempts": {
      "type": "integer",
      "minimum": 0
    },
    "transaction_id": {
      "type": "string",
      "description": "The resolver's transaction (the BLNK transaction at KulOffice)."
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that decided it, when a person did."
    },
    "next_attempt_at": {
      "type": "string",
      "format": "date-time"
    },
    "error_class": {
      "enum": [
        "timeout",
        "no_answer",
        "refused",
        "inconclusive"
      ]
    }
  },
  "additionalProperties": false
}

cash_in.application_review_required

The credit is unresolved after its window (24 h) or its resolver cannot apply credits (IO-2.4): a person decides, with four eyes, whether it was applied, is rejected or is sent again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.application_review_required/v1",
  "title": "cash_in.application_review_required",
  "description": "The credit is unresolved after its window (24 h) or its resolver cannot apply credits (IO-2.4): a person decides, with four eyes, whether it was applied, is rejected or is sent again.",
  "x-kulpay": {
    "aggregate": "deposit_credit",
    "webhook": false,
    "terminal": false,
    "act": "An operator checks the resolver and resolves the credit with four eyes."
  },
  "type": "object",
  "required": [
    "credit_id",
    "deposit_id",
    "intent_id",
    "lookup_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "status",
    "attempts",
    "reason"
  ],
  "properties": {
    "credit_id": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "pending",
        "unknown",
        "applied",
        "rejected",
        "review_required"
      ]
    },
    "attempts": {
      "type": "integer",
      "minimum": 0
    },
    "transaction_id": {
      "type": "string",
      "description": "The resolver's transaction (the BLNK transaction at KulOffice)."
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that decided it, when a person did."
    }
  },
  "additionalProperties": false
}

cash_in.credit_reversal_failed

The resolver refused to take the credit back, or did not answer within 72 hours (IO-2.7): a credit_reversal_failed exception waits for an operator. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.credit_reversal_failed/v1",
  "title": "cash_in.credit_reversal_failed",
  "description": "The resolver refused to take the credit back, or did not answer within 72 hours (IO-2.7): a credit_reversal_failed exception waits for an operator.",
  "x-kulpay": {
    "aggregate": "deposit_credit",
    "webhook": false,
    "terminal": false,
    "act": "An operator works the exception."
  },
  "type": "object",
  "required": [
    "credit_id",
    "deposit_id",
    "intent_id",
    "lookup_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "status",
    "attempts",
    "error"
  ],
  "properties": {
    "credit_id": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "pending",
        "unknown",
        "applied",
        "rejected",
        "review_required"
      ]
    },
    "attempts": {
      "type": "integer",
      "minimum": 0
    },
    "transaction_id": {
      "type": "string",
      "description": "The resolver's transaction (the BLNK transaction at KulOffice)."
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that decided it, when a person did."
    },
    "next_attempt_at": {
      "type": "string",
      "format": "date-time"
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

cash_in.credit_reversal_requested

A second operator approved taking an applied credit back, the bank having returned the deposit (IO-2.7): KulPay asks the resolver to reverse it until it answers. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.credit_reversal_requested/v1",
  "title": "cash_in.credit_reversal_requested",
  "description": "A second operator approved taking an applied credit back, the bank having returned the deposit (IO-2.7): KulPay asks the resolver to reverse it until it answers.",
  "x-kulpay": {
    "aggregate": "deposit_credit",
    "webhook": false,
    "terminal": false,
    "act": "None: KulPay sends the reversal."
  },
  "type": "object",
  "required": [
    "credit_id",
    "deposit_id",
    "intent_id",
    "lookup_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "status",
    "attempts",
    "reason",
    "approval_id"
  ],
  "properties": {
    "credit_id": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "pending",
        "unknown",
        "applied",
        "rejected",
        "review_required"
      ]
    },
    "attempts": {
      "type": "integer",
      "minimum": 0
    },
    "transaction_id": {
      "type": "string",
      "description": "The resolver's transaction (the BLNK transaction at KulOffice)."
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that decided it, when a person did."
    },
    "next_attempt_at": {
      "type": "string",
      "format": "date-time"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

cash_in.credit_reversal_retry_scheduled

A credit's reversal got no answer KulPay could read (IO-2.7): the same reversal is sent again, the resolver keying it by the credit. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.credit_reversal_retry_scheduled/v1",
  "title": "cash_in.credit_reversal_retry_scheduled",
  "description": "A credit's reversal got no answer KulPay could read (IO-2.7): the same reversal is sent again, the resolver keying it by the credit.",
  "x-kulpay": {
    "aggregate": "deposit_credit",
    "webhook": false,
    "terminal": false,
    "act": "None: KulPay attempts again on schedule."
  },
  "type": "object",
  "required": [
    "credit_id",
    "deposit_id",
    "intent_id",
    "lookup_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "status",
    "attempts",
    "next_attempt_at",
    "error_class"
  ],
  "properties": {
    "credit_id": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "pending",
        "unknown",
        "applied",
        "rejected",
        "review_required"
      ]
    },
    "attempts": {
      "type": "integer",
      "minimum": 0
    },
    "transaction_id": {
      "type": "string",
      "description": "The resolver's transaction (the BLNK transaction at KulOffice)."
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that decided it, when a person did."
    },
    "next_attempt_at": {
      "type": "string",
      "format": "date-time"
    },
    "error_class": {
      "type": "string",
      "description": "The class of the failure, never its text: no_answer."
    }
  },
  "additionalProperties": false
}

cash_in.credit_reversed

The resolver took the credit back (IO-2.7): it debited the customer or, short of balance, recorded the debt and held the wallet for withdrawals. The deposit is returned (cash_in.returned). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.credit_reversed/v1",
  "title": "cash_in.credit_reversed",
  "description": "The resolver took the credit back (IO-2.7): it debited the customer or, short of balance, recorded the debt and held the wallet for withdrawals. The deposit is returned (cash_in.returned).",
  "x-kulpay": {
    "aggregate": "deposit_credit",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "credit_id",
    "deposit_id",
    "intent_id",
    "lookup_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "status",
    "attempts",
    "reversal_transaction_id",
    "wallet_held"
  ],
  "properties": {
    "credit_id": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "pending",
        "unknown",
        "applied",
        "rejected",
        "review_required"
      ]
    },
    "attempts": {
      "type": "integer",
      "minimum": 0
    },
    "transaction_id": {
      "type": "string",
      "description": "The resolver's transaction (the BLNK transaction at KulOffice)."
    },
    "reason": {
      "type": "string"
    },
    "approval_id": {
      "type": "string",
      "description": "The four-eyes approval that decided it, when a person did."
    },
    "next_attempt_at": {
      "type": "string",
      "format": "date-time"
    },
    "reversal_transaction_id": {
      "type": "string"
    },
    "debt": {
      "type": "string",
      "description": "What the customer could not cover, owed to KulOffice."
    },
    "wallet_held": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

cash_deposit events

EventWebhookTerminalFires whenWhat to do
cash_in.bill_paid v1 no no A confirmed deposit's bill was paid (IO-2.5): the deposit is credited for the partner, cash_in.completed carrying the bill payment and its receipt. None: cash_in.completed or cash_in.suspended follows.
cash_in.bill_requested v1 no no A confirmed deposit's bill was quoted and is being paid from the account the deposit credited (IO-2.5): the largest amount whose total with the payer fee the deposit covers; remainder_minor is what the deposit leaves over (fee rounding). None: cash_in.completed or cash_in.suspended follows.
cash_in.bill_retry_scheduled v1 no no A confirmed deposit's bill is tried or looked at again (IO-2.5): it could not be quoted yet (the biller unavailable), or its payment is still under way. None: cash_in.completed or cash_in.suspended follows.
cash_in.confirmed v2 yes yes A cash deposit matched a line of the receiving account's statement (same entry ID, amount and currency) and was credited to the merchant's available balance (PSP-7.6). An entry ID is credited at most once. v2 (IO-2.2) carries the intent the deposit confirmed and its lookup, so the resolver can correlate the credit with its answer, and the destination kind. The money is in your available balance.
cash_in.exception v1 no yes A cash deposit could not be confirmed (PSP-7.6): unconfirmed_deposit (no statement line by the end of the statement profile's arrival window) or deposit_amount_mismatch (the statement line's amount differs). Nothing is credited. Check with the bank: a statement may be missing, or the confirmation wrong.
cash_in.reconciled v1 no yes A deposit credited on its confirmation (the PSP's credit_on confirmation) was matched to its statement line: same entry, amount and currency. A line with another amount, or none by the end of the arrival window, is cash_in.exception instead; the credit stands either way. None: reconciled.
cash_in.rejected v1 no yes A deposit the bank never showed on its statement (unconfirmed) was rejected by treasury, with a second person approving (QA T-107): nothing was credited; the partner's outcome is failed. Tell the depositor the deposit was not received; nothing to send back.
cash_in.reported v1 no no A bank confirmed a cash deposit (PSP-7.6): the statement entry ID, value date, amount, currency and the depositor's KulPay account or tag. Nothing is credited until a line of the receiving account's statement confirms it. None: confirmed, suspended or an exception follows.
cash_in.returned v2 no yes A suspended cash deposit was returned (PSP-7.6), with a second person approving. v2 (IO-2.7): also a deposit the bank returned after it was credited to a wallet, once the resolver took the credit back (basis credit_reversal, with the credit and the reversal's transaction). Send the money back to the depositor through the bank.
cash_in.suspended v3 no no A confirmed cash deposit named no valid KulPay account or tag (unidentified_deposit) and was put in suspense until an analyst assigns or returns it, with a second person approving (PSP-7.6). v2 (IO-2.6): a deposit whose credit at the resolver was rejected also comes here, with code credit_rejected, the credit and the account it was taken back from; the same assign or return, with four eyes, decides it. v3 (IO-2.5): a deposit for a bill whose payment failed (bill_failed, with the bill payment), or whose biller credit account was withdrawn since the lookup (credit_account_invalid), is taken back from the account it credited into suspense the same way. Assign it to an account or return it (POST /v1/admin/cash-deposits/{id}/resolve).

cash_in.bill_paid

A confirmed deposit's bill was paid (IO-2.5): the deposit is credited for the partner, cash_in.completed carrying the bill payment and its receipt. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.bill_paid/v1",
  "title": "cash_in.bill_paid",
  "description": "A confirmed deposit's bill was paid (IO-2.5): the deposit is credited for the partner, cash_in.completed carrying the bill payment and its receipt.",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": false,
    "act": "None: cash_in.completed or cash_in.suspended follows."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "deposit_bill_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "attempts",
    "bill_payment_id"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "deposit_bill_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "description": "What the deposit brought for the bill."
    },
    "currency": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    },
    "bill_payment_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

cash_in.bill_requested

A confirmed deposit's bill was quoted and is being paid from the account the deposit credited (IO-2.5): the largest amount whose total with the payer fee the deposit covers; remainder_minor is what the deposit leaves over (fee rounding). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.bill_requested/v1",
  "title": "cash_in.bill_requested",
  "description": "A confirmed deposit's bill was quoted and is being paid from the account the deposit credited (IO-2.5): the largest amount whose total with the payer fee the deposit covers; remainder_minor is what the deposit leaves over (fee rounding).",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": false,
    "act": "None: cash_in.completed or cash_in.suspended follows."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "deposit_bill_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "attempts",
    "bill_payment_id",
    "bill_amount_minor"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "deposit_bill_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "description": "What the deposit brought for the bill."
    },
    "currency": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    },
    "bill_payment_id": {
      "type": "string"
    },
    "bill_amount_minor": {
      "type": "integer"
    },
    "remainder_minor": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

cash_in.bill_retry_scheduled

A confirmed deposit's bill is tried or looked at again (IO-2.5): it could not be quoted yet (the biller unavailable), or its payment is still under way. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.bill_retry_scheduled/v1",
  "title": "cash_in.bill_retry_scheduled",
  "description": "A confirmed deposit's bill is tried or looked at again (IO-2.5): it could not be quoted yet (the biller unavailable), or its payment is still under way.",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": false,
    "act": "None: cash_in.completed or cash_in.suspended follows."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "deposit_bill_id",
    "destination_kind",
    "amount_minor",
    "currency",
    "attempts",
    "next_attempt_at"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "deposit_bill_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "description": "What the deposit brought for the bill."
    },
    "currency": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    },
    "bill_payment_id": {
      "type": "string"
    },
    "error": {
      "type": "string"
    },
    "bill_status": {
      "type": "string"
    },
    "next_attempt_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

cash_in.confirmed

A cash deposit matched a line of the receiving account's statement (same entry ID, amount and currency) and was credited to the merchant's available balance (PSP-7.6). An entry ID is credited at most once. v2 (IO-2.2) carries the intent the deposit confirmed and its lookup, so the resolver can correlate the credit with its answer, and the destination kind. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.confirmed/v2",
  "title": "cash_in.confirmed",
  "description": "A cash deposit matched a line of the receiving account's statement (same entry ID, amount and currency) and was credited to the merchant's available balance (PSP-7.6). An entry ID is credited at most once. v2 (IO-2.2) carries the intent the deposit confirmed and its lookup, so the resolver can correlate the credit with its answer, and the destination kind.",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": true,
    "terminal": true,
    "act": "The money is in your available balance."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "psp_id",
    "account",
    "entry_id",
    "amount_minor",
    "currency",
    "value_date",
    "status",
    "credited_account_id"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "value_date": {
      "type": "string"
    },
    "depositor_ref": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "reported",
        "confirmed",
        "suspended",
        "returned",
        "unconfirmed",
        "mismatch"
      ]
    },
    "credited_account_id": {
      "type": "string"
    },
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ],
      "description": "Where the deposit goes, as the resolver decided (IO-1.3)."
    }
  },
  "additionalProperties": false
}

cash_in.exception

A cash deposit could not be confirmed (PSP-7.6): unconfirmed_deposit (no statement line by the end of the statement profile's arrival window) or deposit_amount_mismatch (the statement line's amount differs). Nothing is credited. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.exception/v1",
  "title": "cash_in.exception",
  "description": "A cash deposit could not be confirmed (PSP-7.6): unconfirmed_deposit (no statement line by the end of the statement profile's arrival window) or deposit_amount_mismatch (the statement line's amount differs). Nothing is credited.",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": true,
    "act": "Check with the bank: a statement may be missing, or the confirmation wrong."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "psp_id",
    "account",
    "entry_id",
    "amount_minor",
    "currency",
    "value_date",
    "status",
    "code",
    "reason"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "value_date": {
      "type": "string"
    },
    "depositor_ref": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "reported",
        "confirmed",
        "suspended",
        "returned",
        "unconfirmed",
        "mismatch"
      ]
    },
    "code": {
      "type": "string",
      "enum": [
        "unconfirmed_deposit",
        "deposit_amount_mismatch"
      ]
    },
    "reason": {
      "type": "string"
    },
    "statement_amount_minor": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

cash_in.reconciled

A deposit credited on its confirmation (the PSP's credit_on confirmation) was matched to its statement line: same entry, amount and currency. A line with another amount, or none by the end of the arrival window, is cash_in.exception instead; the credit stands either way. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.reconciled/v1",
  "title": "cash_in.reconciled",
  "description": "A deposit credited on its confirmation (the PSP's credit_on confirmation) was matched to its statement line: same entry, amount and currency. A line with another amount, or none by the end of the arrival window, is cash_in.exception instead; the credit stands either way.",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": true,
    "act": "None: reconciled."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "psp_id",
    "account",
    "entry_id",
    "amount_minor",
    "currency",
    "value_date",
    "status",
    "canonical_line_id"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "value_date": {
      "type": "string"
    },
    "depositor_ref": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "reported",
        "confirmed",
        "suspended",
        "returned",
        "unconfirmed",
        "mismatch"
      ]
    },
    "canonical_line_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

cash_in.rejected

A deposit the bank never showed on its statement (unconfirmed) was rejected by treasury, with a second person approving (QA T-107): nothing was credited; the partner's outcome is failed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.rejected/v1",
  "title": "cash_in.rejected",
  "description": "A deposit the bank never showed on its statement (unconfirmed) was rejected by treasury, with a second person approving (QA T-107): nothing was credited; the partner's outcome is failed.",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": true,
    "act": "Tell the depositor the deposit was not received; nothing to send back."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "psp_id",
    "account",
    "entry_id",
    "amount_minor",
    "currency",
    "value_date",
    "status",
    "reason"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "value_date": {
      "type": "string"
    },
    "depositor_ref": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "reported",
        "confirmed",
        "suspended",
        "returned",
        "unconfirmed",
        "mismatch",
        "rejected"
      ]
    },
    "reason": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

cash_in.reported

A bank confirmed a cash deposit (PSP-7.6): the statement entry ID, value date, amount, currency and the depositor's KulPay account or tag. Nothing is credited until a line of the receiving account's statement confirms it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.reported/v1",
  "title": "cash_in.reported",
  "description": "A bank confirmed a cash deposit (PSP-7.6): the statement entry ID, value date, amount, currency and the depositor's KulPay account or tag. Nothing is credited until a line of the receiving account's statement confirms it.",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": false,
    "act": "None: confirmed, suspended or an exception follows."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "psp_id",
    "account",
    "entry_id",
    "amount_minor",
    "currency",
    "value_date",
    "status"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "value_date": {
      "type": "string"
    },
    "depositor_ref": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "reported",
        "confirmed",
        "suspended",
        "returned",
        "unconfirmed",
        "mismatch"
      ]
    }
  },
  "additionalProperties": false
}

cash_in.returned

A suspended cash deposit was returned (PSP-7.6), with a second person approving. v2 (IO-2.7): also a deposit the bank returned after it was credited to a wallet, once the resolver took the credit back (basis credit_reversal, with the credit and the reversal's transaction). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.returned/v2",
  "title": "cash_in.returned",
  "description": "A suspended cash deposit was returned (PSP-7.6), with a second person approving. v2 (IO-2.7): also a deposit the bank returned after it was credited to a wallet, once the resolver took the credit back (basis credit_reversal, with the credit and the reversal's transaction).",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": true,
    "act": "Send the money back to the depositor through the bank."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "psp_id",
    "account",
    "entry_id",
    "amount_minor",
    "currency",
    "value_date",
    "status",
    "reason"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "value_date": {
      "type": "string"
    },
    "depositor_ref": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "reported",
        "confirmed",
        "suspended",
        "returned",
        "unconfirmed",
        "mismatch"
      ]
    },
    "reason": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    },
    "basis": {
      "enum": [
        "suspense",
        "credit_reversal"
      ]
    },
    "credit_id": {
      "type": "string"
    },
    "reversal_transaction_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

cash_in.suspended

A confirmed cash deposit named no valid KulPay account or tag (unidentified_deposit) and was put in suspense until an analyst assigns or returns it, with a second person approving (PSP-7.6). v2 (IO-2.6): a deposit whose credit at the resolver was rejected also comes here, with code credit_rejected, the credit and the account it was taken back from; the same assign or return, with four eyes, decides it. v3 (IO-2.5): a deposit for a bill whose payment failed (bill_failed, with the bill payment), or whose biller credit account was withdrawn since the lookup (credit_account_invalid), is taken back from the account it credited into suspense the same way. Schema (JSON)

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.suspended/v3",
  "title": "cash_in.suspended",
  "description": "A confirmed cash deposit named no valid KulPay account or tag (unidentified_deposit) and was put in suspense until an analyst assigns or returns it, with a second person approving (PSP-7.6). v2 (IO-2.6): a deposit whose credit at the resolver was rejected also comes here, with code credit_rejected, the credit and the account it was taken back from; the same assign or return, with four eyes, decides it. v3 (IO-2.5): a deposit for a bill whose payment failed (bill_failed, with the bill payment), or whose biller credit account was withdrawn since the lookup (credit_account_invalid), is taken back from the account it credited into suspense the same way.",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": false,
    "act": "Assign it to an account or return it (POST /v1/admin/cash-deposits/{id}/resolve)."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "psp_id",
    "account",
    "entry_id",
    "amount_minor",
    "currency",
    "value_date",
    "status",
    "reason"
  ],
  "properties": {
    "deposit_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "value_date": {
      "type": "string"
    },
    "depositor_ref": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "reported",
        "confirmed",
        "suspended",
        "returned",
        "unconfirmed",
        "mismatch"
      ]
    },
    "reason": {
      "type": "string"
    },
    "code": {
      "enum": [
        "unidentified_deposit",
        "credit_rejected",
        "bill_failed",
        "credit_account_invalid"
      ]
    },
    "credit_id": {
      "type": "string",
      "description": "The rejected credit (IO-2.4)."
    },
    "reversed_from_account_id": {
      "type": "string",
      "description": "The account the deposit was credited to and taken back from."
    },
    "deposit_bill_id": {
      "type": "string"
    },
    "bill_payment_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

cash_deposit_intent events

EventWebhookTerminalFires whenWhat to do
cash_in.completed v2 yes yes A confirmed deposit reached its terminal outcome for the partner that took the cash: credited (the beneficiary has the money: credited on confirmation or on the statement, as the PSP chose, and applied at the resolver when it goes to a wallet), returned (rejected or unidentified, then returned from suspense with four eyes) or failed (credit_on statement: no statement line, or another amount). Until then the deposit is processing and not final. v2 (IO-2.5): a deposit for an invoice or a biller's product is credited once its bill is paid, and carries the bill payment and its signed receipt. The partner closes the deposit: tells the depositor it was credited, or returns the cash.
cash_in.initiated v2 yes no A partner bank or agent initiated a cash deposit against a reference lookup (PSP-7.8): the beneficiary, amount and currency are fixed; nothing is credited until the deposit is confirmed and the statement shows it. The metadata is never in the event, only its keys. v2 (IO-2.1) carries the resolver's decisions the intent inherited: the destination kind, the charge and who pays it, the settlement mode, and the total the depositor pays (the amount plus the charge when the customer pays it). The KulPay CMS can show the deposit as expected. None for KulPay.
cash_in.initiation_confirmed v1 yes no An initiated deposit was confirmed with its bank statement entry (PSP-7.9) and reported as a cash deposit (cash_in.reported); it is credited when the statement line matches. None.
cash_in.initiation_expired v1 yes no An initiated deposit was not confirmed within its window (24 hours) and expired (PSP-7.8). Any cash taken against it must be confirmed through a new lookup, or returned. The partner checks whether the cash was taken; the CMS stops expecting the deposit.

cash_in.completed

A confirmed deposit reached its terminal outcome for the partner that took the cash: credited (the beneficiary has the money: credited on confirmation or on the statement, as the PSP chose, and applied at the resolver when it goes to a wallet), returned (rejected or unidentified, then returned from suspense with four eyes) or failed (credit_on statement: no statement line, or another amount). Until then the deposit is processing and not final. v2 (IO-2.5): a deposit for an invoice or a biller's product is credited once its bill is paid, and carries the bill payment and its signed receipt. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.completed/v2",
  "title": "cash_in.completed",
  "description": "A confirmed deposit reached its terminal outcome for the partner that took the cash: credited (the beneficiary has the money: credited on confirmation or on the statement, as the PSP chose, and applied at the resolver when it goes to a wallet), returned (rejected or unidentified, then returned from suspense with four eyes) or failed (credit_on statement: no statement line, or another amount). Until then the deposit is processing and not final. v2 (IO-2.5): a deposit for an invoice or a biller's product is credited once its bill is paid, and carries the bill payment and its signed receipt.",
  "x-kulpay": {
    "aggregate": "cash_deposit_intent",
    "webhook": true,
    "terminal": true,
    "act": "The partner closes the deposit: tells the depositor it was credited, or returns the cash."
  },
  "type": "object",
  "required": [
    "intent_id",
    "lookup_id",
    "psp_id",
    "amount_minor",
    "currency",
    "status",
    "expires_at",
    "deposit_id",
    "outcome"
  ],
  "properties": {
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "initiated",
        "reported",
        "expired"
      ]
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "partner_reference": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "outcome": {
      "enum": [
        "credited",
        "returned",
        "failed"
      ]
    },
    "bill_payment_id": {
      "type": "string",
      "description": "The bill the deposit paid (IO-2.5)."
    },
    "receipt_url": {
      "type": "string",
      "description": "The bill payment's signed receipt (BIL-2.6), with its token or proof of payment, for the depositor."
    }
  },
  "additionalProperties": false
}

cash_in.initiated

A partner bank or agent initiated a cash deposit against a reference lookup (PSP-7.8): the beneficiary, amount and currency are fixed; nothing is credited until the deposit is confirmed and the statement shows it. The metadata is never in the event, only its keys. v2 (IO-2.1) carries the resolver's decisions the intent inherited: the destination kind, the charge and who pays it, the settlement mode, and the total the depositor pays (the amount plus the charge when the customer pays it). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.initiated/v2",
  "title": "cash_in.initiated",
  "description": "A partner bank or agent initiated a cash deposit against a reference lookup (PSP-7.8): the beneficiary, amount and currency are fixed; nothing is credited until the deposit is confirmed and the statement shows it. The metadata is never in the event, only its keys. v2 (IO-2.1) carries the resolver's decisions the intent inherited: the destination kind, the charge and who pays it, the settlement mode, and the total the depositor pays (the amount plus the charge when the customer pays it).",
  "x-kulpay": {
    "aggregate": "cash_deposit_intent",
    "webhook": true,
    "terminal": false,
    "act": "The KulPay CMS can show the deposit as expected. None for KulPay."
  },
  "type": "object",
  "required": [
    "intent_id",
    "lookup_id",
    "psp_id",
    "amount_minor",
    "currency",
    "status",
    "expires_at"
  ],
  "properties": {
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "initiated",
        "reported",
        "expired"
      ]
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "partner_reference": {
      "type": "string"
    },
    "reference_masked": {
      "type": "string"
    },
    "depositor_metadata_keys": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "beneficiary_metadata_keys": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ],
      "description": "Where the deposit goes, as the resolver decided (IO-1.3)."
    },
    "charge_payer": {
      "enum": [
        "customer",
        "kuloffice",
        "biller"
      ]
    },
    "charge_minor": {
      "type": "integer",
      "minimum": 0
    },
    "settlement_mode": {
      "enum": [
        "immediate",
        "batch"
      ]
    },
    "total_minor": {
      "type": "integer",
      "description": "What the depositor pays, in minor units."
    }
  },
  "additionalProperties": false
}

cash_in.initiation_confirmed

An initiated deposit was confirmed with its bank statement entry (PSP-7.9) and reported as a cash deposit (cash_in.reported); it is credited when the statement line matches. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.initiation_confirmed/v1",
  "title": "cash_in.initiation_confirmed",
  "description": "An initiated deposit was confirmed with its bank statement entry (PSP-7.9) and reported as a cash deposit (cash_in.reported); it is credited when the statement line matches.",
  "x-kulpay": {
    "aggregate": "cash_deposit_intent",
    "webhook": true,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "intent_id",
    "lookup_id",
    "psp_id",
    "amount_minor",
    "currency",
    "status",
    "expires_at",
    "deposit_id",
    "entry_id"
  ],
  "properties": {
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "initiated",
        "reported",
        "expired"
      ]
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "partner_reference": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

cash_in.initiation_expired

An initiated deposit was not confirmed within its window (24 hours) and expired (PSP-7.8). Any cash taken against it must be confirmed through a new lookup, or returned. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.initiation_expired/v1",
  "title": "cash_in.initiation_expired",
  "description": "An initiated deposit was not confirmed within its window (24 hours) and expired (PSP-7.8). Any cash taken against it must be confirmed through a new lookup, or returned.",
  "x-kulpay": {
    "aggregate": "cash_deposit_intent",
    "webhook": true,
    "terminal": false,
    "act": "The partner checks whether the cash was taken; the CMS stops expecting the deposit."
  },
  "type": "object",
  "required": [
    "intent_id",
    "lookup_id",
    "psp_id",
    "amount_minor",
    "currency",
    "status",
    "expires_at"
  ],
  "properties": {
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "enum": [
        "initiated",
        "reported",
        "expired"
      ]
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "partner_reference": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

credit_reconciliation events

EventWebhookTerminalFires whenWhat to do
cash_in.credits_reconciled v1 no no A resolver reported its reconciliation of a day's credits (IO-6.2): what KulPay applied, what its ledger (BLNK) posted, what was settled, and what is missing on each side. closed when the three agree and nothing is missing; differences otherwise, each an exception (missing_at_kuloffice, missing_at_kulpay; IO-6.3). Work the exceptions of a day with differences.

cash_in.credits_reconciled

A resolver reported its reconciliation of a day's credits (IO-6.2): what KulPay applied, what its ledger (BLNK) posted, what was settled, and what is missing on each side. closed when the three agree and nothing is missing; differences otherwise, each an exception (missing_at_kuloffice, missing_at_kulpay; IO-6.3). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.credits_reconciled/v1",
  "title": "cash_in.credits_reconciled",
  "description": "A resolver reported its reconciliation of a day's credits (IO-6.2): what KulPay applied, what its ledger (BLNK) posted, what was settled, and what is missing on each side. closed when the three agree and nothing is missing; differences otherwise, each an exception (missing_at_kuloffice, missing_at_kulpay; IO-6.3).",
  "x-kulpay": {
    "aggregate": "credit_reconciliation",
    "webhook": false,
    "terminal": false,
    "act": "Work the exceptions of a day with differences."
  },
  "type": "object",
  "required": [
    "resolver_psp_id",
    "date",
    "currency",
    "applied",
    "ledger",
    "settled",
    "missing_at_kuloffice",
    "missing_at_kulpay",
    "status"
  ],
  "properties": {
    "resolver_psp_id": {
      "type": "string"
    },
    "date": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "applied": {
      "type": "string"
    },
    "ledger": {
      "type": "string"
    },
    "settled": {
      "type": "string"
    },
    "missing_at_kuloffice": {
      "type": "integer"
    },
    "missing_at_kulpay": {
      "type": "integer"
    },
    "status": {
      "enum": [
        "closed",
        "differences"
      ]
    }
  },
  "additionalProperties": false
}

code_mapping events

EventWebhookTerminalFires whenWhat to do
code_mapping.activated v1 no no A provider's response-code mapping version was approved and is now used. None.
code_mapping.version_created v1 no no A new version of a provider's response-code mapping was prepared; a second person approves it. Approve or reject it (POST /v1/admin/approvals/{id}/decide).

code_mapping.activated

A provider's response-code mapping version was approved and is now used. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/code_mapping.activated/v1",
  "title": "code_mapping.activated",
  "description": "A provider's response-code mapping version was approved and is now used.",
  "x-kulpay": {
    "aggregate": "code_mapping",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "provider",
    "version",
    "mapping_id"
  ],
  "properties": {
    "provider": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "mapping_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

code_mapping.version_created

A new version of a provider's response-code mapping was prepared; a second person approves it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/code_mapping.version_created/v1",
  "title": "code_mapping.version_created",
  "description": "A new version of a provider's response-code mapping was prepared; a second person approves it.",
  "x-kulpay": {
    "aggregate": "code_mapping",
    "webhook": false,
    "terminal": false,
    "act": "Approve or reject it (POST /v1/admin/approvals/{id}/decide)."
  },
  "type": "object",
  "required": [
    "provider",
    "version",
    "mapping_id"
  ],
  "properties": {
    "provider": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "mapping_id": {
      "type": "string"
    },
    "entries": {
      "type": "integer"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

compliance_case events

EventWebhookTerminalFires whenWhat to do
compliance_case.closed v1 no no A suspicious-activity case was closed without a communication, the decision justified by one compliance officer and approved by another (F3-8). None.
compliance_case.noted v1 no no A step of a suspicious-activity case's investigation was recorded (F3-8). None.
compliance_case.opened v1 no no A suspicious-activity case was opened (F3-8), by an operator or by a risk rule marked suspicion. The platform's, never the account's: it reaches no tenant feed, webhook or portal (no tipping-off). Investigate.
compliance_case.reported v1 no no A suspicious-activity case was communicated to GIFiM, decided by one compliance officer and approved by another (F3-8). Keep the communication's reference.

compliance_case.closed

A suspicious-activity case was closed without a communication, the decision justified by one compliance officer and approved by another (F3-8). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/compliance_case.closed/v1",
  "title": "compliance_case.closed",
  "description": "A suspicious-activity case was closed without a communication, the decision justified by one compliance officer and approved by another (F3-8).",
  "x-kulpay": {
    "aggregate": "compliance_case",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "case_id",
    "subject_type",
    "subject_id",
    "status",
    "source",
    "decision",
    "decided_by",
    "approved_by"
  ],
  "properties": {
    "case_id": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "open",
        "decision_due",
        "reported",
        "closed"
      ]
    },
    "source": {
      "enum": [
        "operator",
        "risk_rule"
      ]
    },
    "decision": {
      "enum": [
        "no_report"
      ]
    },
    "decided_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    },
    "reference": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

compliance_case.noted

A step of a suspicious-activity case's investigation was recorded (F3-8). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/compliance_case.noted/v1",
  "title": "compliance_case.noted",
  "description": "A step of a suspicious-activity case's investigation was recorded (F3-8).",
  "x-kulpay": {
    "aggregate": "compliance_case",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "case_id",
    "subject_type",
    "subject_id",
    "status",
    "source",
    "by"
  ],
  "properties": {
    "case_id": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "open",
        "decision_due",
        "reported",
        "closed"
      ]
    },
    "source": {
      "enum": [
        "operator",
        "risk_rule"
      ]
    },
    "by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

compliance_case.opened

A suspicious-activity case was opened (F3-8), by an operator or by a risk rule marked suspicion. The platform's, never the account's: it reaches no tenant feed, webhook or portal (no tipping-off). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/compliance_case.opened/v1",
  "title": "compliance_case.opened",
  "description": "A suspicious-activity case was opened (F3-8), by an operator or by a risk rule marked suspicion. The platform's, never the account's: it reaches no tenant feed, webhook or portal (no tipping-off).",
  "x-kulpay": {
    "aggregate": "compliance_case",
    "webhook": false,
    "terminal": false,
    "act": "Investigate."
  },
  "type": "object",
  "required": [
    "case_id",
    "subject_type",
    "subject_id",
    "status",
    "source",
    "opened_by"
  ],
  "properties": {
    "case_id": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "open",
        "decision_due",
        "reported",
        "closed"
      ]
    },
    "source": {
      "enum": [
        "operator",
        "risk_rule"
      ]
    },
    "rule_id": {
      "type": "string"
    },
    "opened_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

compliance_case.reported

A suspicious-activity case was communicated to GIFiM, decided by one compliance officer and approved by another (F3-8). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/compliance_case.reported/v1",
  "title": "compliance_case.reported",
  "description": "A suspicious-activity case was communicated to GIFiM, decided by one compliance officer and approved by another (F3-8).",
  "x-kulpay": {
    "aggregate": "compliance_case",
    "webhook": false,
    "terminal": false,
    "act": "Keep the communication's reference."
  },
  "type": "object",
  "required": [
    "case_id",
    "subject_type",
    "subject_id",
    "status",
    "source",
    "decision",
    "decided_by",
    "approved_by"
  ],
  "properties": {
    "case_id": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "open",
        "decision_due",
        "reported",
        "closed"
      ]
    },
    "source": {
      "enum": [
        "operator",
        "risk_rule"
      ]
    },
    "decision": {
      "enum": [
        "report"
      ]
    },
    "decided_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    },
    "reference": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp events

EventWebhookTerminalFires whenWhat to do
config.capability_matrix.activated v1 no no A version of the PSP's capability_matrix configuration became active; the previous active version is superseded. Payments from now on record this version. None.
config.capability_matrix.version_created v1 no no A new version of the PSP's capability_matrix configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. None; an approver decides the pending approval when one is needed.
config.commercial_terms.activated v1 no no A version of the PSP's commercial_terms configuration became active; the previous active version is superseded. Payments from now on record this version. None.
config.commercial_terms.version_created v1 no no A new version of the PSP's commercial_terms configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. None; an approver decides the pending approval when one is needed.
config.orientation_profile.activated v1 no no A version of the PSP's orientation_profile configuration became active; the previous active version is superseded. Payments from now on record this version. None.
config.orientation_profile.version_created v1 no no A new version of the PSP's orientation_profile configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. None; an approver decides the pending approval when one is needed.
config.retry_policy.activated v1 no no A version of the PSP's retry_policy configuration became active; the previous active version is superseded. Payments from now on record this version. None.
config.retry_policy.version_created v1 no no A new version of the PSP's retry_policy configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. None; an approver decides the pending approval when one is needed.
config.routing.activated v1 no no A version of the PSP's routing configuration became active; the previous active version is superseded. Payments from now on record this version. None.
config.routing.version_created v1 no no A new version of the PSP's routing configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. None; an approver decides the pending approval when one is needed.
config.statement_profile.activated v1 no no A version of the PSP's statement_profile configuration became active; the previous active version is superseded. Payments from now on record this version. None.
config.statement_profile.version_created v1 no no A new version of the PSP's statement_profile configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. None; an approver decides the pending approval when one is needed.
config.technical_config.activated v1 no no A version of the PSP's technical_config configuration became active; the previous active version is superseded. Payments from now on record this version. None.
config.technical_config.version_created v1 no no A new version of the PSP's technical_config configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. None; an approver decides the pending approval when one is needed.
credential.psp_key.registered v1 no no A PSP registered a public key (PSP-11.2): SSH for KulPay-hosted SFTP login, or PGP to verify its statement signatures. None.
credential.psp_key.revoked v1 no no A PSP revoked one of its SSH or PGP public keys (PSP-11.2); it stops working at once. None.
psp.applied.entered v1 no no A PSP entered the applied state. Audit record of the lifecycle; see the PSP page.
psp.certification.case_failed v1 no no A certification case failed: what was expected and what happened, with the payment it created. The PSP fixes its integration and repeats the failed cases.
psp.certification.entered v1 no no A PSP entered the certification state. Audit record of the lifecycle; see the PSP page.
psp.certification.failed v1 no no A certification run finished with mandatory cases failed. The PSP repeats the failed cases after fixing them.
psp.certification.passed v1 no no Every mandatory certification case passed. The report is kept with its SHA-256 and, when a signing key is configured, an Ed25519 signature. A PSP in technical_setup moves to certification. None.
psp.certification.run_started v1 no no A certification run started in the PSP's sandbox: the suite generated from its capability matrix, or only the failed cases of an earlier run. None; the run reports psp.certification.passed or psp.certification.failed.
psp.commercial_setup.entered v1 no no A PSP entered the commercial_setup state. Audit record of the lifecycle; see the PSP page.
psp.commercial_terms.approved v1 no no A second person approved a version of the PSP's commercial terms; it is now active. None.
psp.commercial_terms.version_created v1 no no A new version of the PSP's commercial terms (fees, funds flow, settlement, limits) was prepared for approval. A second person approves or rejects it.
psp.contract.signed v1 no no The signed contract for the PSP's active commercial terms was recorded. It is a pilot gate. None.
psp.go_live.approved v1 no no Two people approved go-live: the live gates were met, and the go-live checklist, with the signed certification report, was stored on the PSP. psp.live.entered follows. None.
psp.health.degraded v1 no no A PSP's circuit for an operation opened (PSP-9.4): too many of its calls failed or ended unknown (by default 50% of at least 20 in 30 s). Routing skips the PSP for that operation; after the open period a few trial calls test it. Check the PSP's status page and contact; traffic is moving to other PSPs where routing allows.
psp.health.recovered v1 no no A PSP's circuit for an operation closed again after its trial calls succeeded (PSP-9.4); routing sends it traffic again. None.
psp.incident.recorded v1 no no An incident was recorded against the PSP. A critical one during pilot holds go-live: the 7 pilot days count again from its resolution. Resolve it with POST /v1/admin/psps/{id}/incidents/{incident}/resolve.
psp.incident.resolved v1 no no An incident against the PSP was resolved. None.
psp.info_requested.entered v1 no no A PSP entered the info_requested state. Audit record of the lifecycle; see the PSP page.
psp.institution_updated v1 no no An operator set the PSP's place in the payment system (IO-4.2): the bank code its NIBs start with and its mobile wallets' prefixes. KulPay infers by them the institution of a biller's credit account (to_psp), and a credit account no known PSP serves blocks the biller's go-live. None.
psp.kyb.approved v1 no no KYB was approved by a second compliance officer (maker-checker). The 1-day clock to live starts. Set up commercial terms.
psp.kyb.document.reviewed v1 no no A compliance officer accepted or rejected a KYB document. None.
psp.kyb.document.uploaded v1 no no A KYB document was uploaded, encrypted, with the checksum of its content. Review the document.
psp.kyb.info_requested v1 no no The reviewer asked the PSP for more information. The PSP provides what is missing.
psp.kyb.rejected v1 no no KYB was rejected. Inform the applicant.
psp.kyb_approved.entered v1 no no A PSP entered the kyb_approved state. Audit record of the lifecycle; see the PSP page.
psp.kyb_review.entered v1 no no A PSP entered the kyb_review state. Audit record of the lifecycle; see the PSP page.
psp.live.entered v1 no no A PSP entered the live state. Audit record of the lifecycle; see the PSP page.
psp.live_key.revealed v1 no no The PSP revealed its live key, issued at pilot. It was shown once and is no longer stored. None.
psp.logo.approved v1 no no A partner's logo was published: an operator approved the partner's upload, or uploaded it (LOGO-1). None: channels get the new logo URL from the biller catalogue.
psp.logo.rejected v1 no no An operator rejected a partner's logo, saying why; the previously approved logo, if any, stays published (LOGO-1). The partner uploads a corrected logo.
psp.logo.submitted v1 no no A partner (PSP or biller) uploaded a logo that passed the checks (PNG, transparent edges, size) and waits for an operator's review (LOGO-1). Review the logo: approve it, or reject it saying why.
psp.offboarded v1 no yes Offboarding finished: no open payments, reconciliation closed. Nothing was deleted. None.
psp.offboarded.entered v1 no no A PSP entered the offboarded state. Audit record of the lifecycle; see the PSP page.
psp.offboarding.entered v1 no no A PSP entered the offboarding state. Audit record of the lifecycle; see the PSP page.
psp.offboarding.started v1 no no Offboarding was approved by two people: the account takes no new payments and every key was revoked. The PSP waits for its payments to be final and its reconciliation closed. Settle and reconcile the open items, then POST /v1/admin/psps/{id}/offboarding/complete.
psp.pilot.entered v1 no no A PSP entered the pilot state. Audit record of the lifecycle; see the PSP page.
psp.pilot.settings_changed v1 no no The pilot limits, merchant allow-list or routing weight changed. Limits never exceed the contracted ones. None.
psp.pilot.setup_failed v1 no no The PSP entered pilot but its pilot settings or live key could not be set up. The move stands. Retry POST /v1/admin/psps/{id}/pilot/setup after fixing the cause.
psp.pilot.started v1 no no The PSP entered pilot for the first time: pilot limits (10% of the contracted limits by default), an empty merchant allow-list, a routing weight, and a live key the PSP reveals once. Add the pilot merchants to the allow-list.
psp.profile.change_decided v1 no no A requested change of a PSP's registered details was approved (and applied) or rejected (PSP-13.3). None.
psp.profile.change_requested v1 no no A PSP admin asked KulPay to change the PSP's registered details (legal name, registration number) in the portal (PSP-13.3); a compliance officer approves or rejects it. A compliance officer checks the change against the PSP's registration documents and decides the approval request on the PSP's page.
psp.profile.contacts_updated v1 no no A PSP admin changed the PSP's technical or compliance contact in the portal (PSP-13.3). The contacts themselves are not recorded in the event. None.
psp.reinstated v1 no no A suspended PSP was reinstated to the state it left; its account and keys work again. None.
psp.rejected.entered v1 no no A PSP entered the rejected state. Audit record of the lifecycle; see the PSP page.
psp.review.completed v1 no no A periodic review was completed; the next one is scheduled by risk tier. None, unless the outcome asks for actions.
psp.review.due v1 no no A PSP's periodic review is due within 30 days, or (overdue: true) is past due. Reviews fall due by risk tier: high every 6 months, medium 12, low 24. Complete the review: POST /v1/admin/psps/{id}/reviews.
psp.risk_tier.set v1 no no The reviewer set the PSP's risk tier, which sets its default limits and review frequency. None.
psp.sandbox.enable_failed v1 no no The PSP entered technical setup but its sandbox could not be opened. The move stands; an operator retries opening the sandbox. Retry POST /v1/admin/psps/{id}/sandbox after fixing the cause.
psp.sandbox.enabled v1 no no The PSP entered technical setup and its sandbox opened: a test-mode key (shown once to the PSP) and test-mode mock connectors for every method. Live keys stay unavailable until pilot. None.
psp.sandbox.key_revealed v1 no no The PSP revealed its sandbox test key. It was shown once and is no longer stored. None.
psp.screening.completed v1 no no A sanctions or PEP screening was performed and its result stored as evidence. A hit or a manual review required needs a decision.
psp.screening.hit v1 no no A screening matched a sanctions or PEP list. Decide whether the match is true before KYB proceeds.
psp.suspended v1 no no The PSP was suspended: its account takes no new payments and its keys are refused on every node from the next request; payments in flight run to their end. Approved by two people, or an emergency suspension by one officer that a second must review within 24 hours. An emergency suspension: review it (approval psp.suspension.review).
psp.suspended.entered v1 no no A PSP entered the suspended state. Audit record of the lifecycle; see the PSP page.
psp.suspension.review_overdue v1 no no An emergency suspension has not been reviewed within 24 hours. A second officer reviews it now.
psp.suspension.reviewed v1 no no A second officer reviewed an emergency suspension: upheld, or overturned (the PSP is reinstated). None.
psp.technical_config.version_created v1 no no A new version of the PSP's technical configuration (endpoints, credentials, webhooks, IP allow-list) was created. None.
psp.technical_setup.entered v1 no no A PSP entered the technical_setup state. Audit record of the lifecycle; see the PSP page.
psp.technical_step.probed v1 no no A setup step's endpoint was dialled over TLS in the background, through the SSRF guard, and the result recorded on the step. A failed probe fails the step until the endpoint is fixed and saved again. If the step failed, fix the endpoint (public address, TLS 1.2+, valid certificate) and save the step again.
psp.technical_step.validated v1 no no A technical setup step was validated. A failed step is not saved; the PSP sees which checks failed. None.
psp.transition.rejected v1 no no A requested lifecycle transition was refused: it is not in the declared table or a gate is not met. None; a control did its job.
retry.budget_exceeded v1 no no Retries to a PSP reached its budget, by default 10% of its requests over a rolling minute with a floor of 10 (PSP-9.6). Further retries wait for the next run; none is dropped. Status queries for unknown outcomes are never held back. Recorded once a minute per PSP. None while it passes; persistent, the PSP is struggling: check its circuits and status.

config.capability_matrix.activated

A version of the PSP's capability_matrix configuration became active; the previous active version is superseded. Payments from now on record this version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.capability_matrix.activated/v1",
  "title": "config.capability_matrix.activated",
  "description": "A version of the PSP's capability_matrix configuration became active; the previous active version is superseded. Payments from now on record this version.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "capability_matrix"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

config.capability_matrix.version_created

A new version of the PSP's capability_matrix configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.capability_matrix.version_created/v1",
  "title": "config.capability_matrix.version_created",
  "description": "A new version of the PSP's capability_matrix configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None; an approver decides the pending approval when one is needed."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "capability_matrix"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "needs_approval": {
      "type": "boolean"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    },
    "rollback_of": {
      "type": "integer",
      "description": "The version this one copies, for a rollback."
    }
  },
  "additionalProperties": false
}

config.commercial_terms.activated

A version of the PSP's commercial_terms configuration became active; the previous active version is superseded. Payments from now on record this version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.commercial_terms.activated/v1",
  "title": "config.commercial_terms.activated",
  "description": "A version of the PSP's commercial_terms configuration became active; the previous active version is superseded. Payments from now on record this version.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "commercial_terms"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

config.commercial_terms.version_created

A new version of the PSP's commercial_terms configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.commercial_terms.version_created/v1",
  "title": "config.commercial_terms.version_created",
  "description": "A new version of the PSP's commercial_terms configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None; an approver decides the pending approval when one is needed."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "commercial_terms"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "needs_approval": {
      "type": "boolean"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    },
    "rollback_of": {
      "type": "integer",
      "description": "The version this one copies, for a rollback."
    }
  },
  "additionalProperties": false
}

config.orientation_profile.activated

A version of the PSP's orientation_profile configuration became active; the previous active version is superseded. Payments from now on record this version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.orientation_profile.activated/v1",
  "title": "config.orientation_profile.activated",
  "description": "A version of the PSP's orientation_profile configuration became active; the previous active version is superseded. Payments from now on record this version.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "orientation_profile"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

config.orientation_profile.version_created

A new version of the PSP's orientation_profile configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.orientation_profile.version_created/v1",
  "title": "config.orientation_profile.version_created",
  "description": "A new version of the PSP's orientation_profile configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None; an approver decides the pending approval when one is needed."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "orientation_profile"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "needs_approval": {
      "type": "boolean"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    },
    "rollback_of": {
      "type": "integer",
      "description": "The version this one copies, for a rollback."
    }
  },
  "additionalProperties": false
}

config.retry_policy.activated

A version of the PSP's retry_policy configuration became active; the previous active version is superseded. Payments from now on record this version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.retry_policy.activated/v1",
  "title": "config.retry_policy.activated",
  "description": "A version of the PSP's retry_policy configuration became active; the previous active version is superseded. Payments from now on record this version.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "retry_policy"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

config.retry_policy.version_created

A new version of the PSP's retry_policy configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.retry_policy.version_created/v1",
  "title": "config.retry_policy.version_created",
  "description": "A new version of the PSP's retry_policy configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None; an approver decides the pending approval when one is needed."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "retry_policy"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "needs_approval": {
      "type": "boolean"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    },
    "rollback_of": {
      "type": "integer",
      "description": "The version this one copies, for a rollback."
    }
  },
  "additionalProperties": false
}

config.routing.activated

A version of the PSP's routing configuration became active; the previous active version is superseded. Payments from now on record this version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.routing.activated/v1",
  "title": "config.routing.activated",
  "description": "A version of the PSP's routing configuration became active; the previous active version is superseded. Payments from now on record this version.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "routing"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

config.routing.version_created

A new version of the PSP's routing configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.routing.version_created/v1",
  "title": "config.routing.version_created",
  "description": "A new version of the PSP's routing configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None; an approver decides the pending approval when one is needed."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "routing"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "needs_approval": {
      "type": "boolean"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    },
    "rollback_of": {
      "type": "integer",
      "description": "The version this one copies, for a rollback."
    }
  },
  "additionalProperties": false
}

config.statement_profile.activated

A version of the PSP's statement_profile configuration became active; the previous active version is superseded. Payments from now on record this version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.statement_profile.activated/v1",
  "title": "config.statement_profile.activated",
  "description": "A version of the PSP's statement_profile configuration became active; the previous active version is superseded. Payments from now on record this version.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "statement_profile"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

config.statement_profile.version_created

A new version of the PSP's statement_profile configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.statement_profile.version_created/v1",
  "title": "config.statement_profile.version_created",
  "description": "A new version of the PSP's statement_profile configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None; an approver decides the pending approval when one is needed."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "statement_profile"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "needs_approval": {
      "type": "boolean"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    },
    "rollback_of": {
      "type": "integer",
      "description": "The version this one copies, for a rollback."
    }
  },
  "additionalProperties": false
}

config.technical_config.activated

A version of the PSP's technical_config configuration became active; the previous active version is superseded. Payments from now on record this version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.technical_config.activated/v1",
  "title": "config.technical_config.activated",
  "description": "A version of the PSP's technical_config configuration became active; the previous active version is superseded. Payments from now on record this version.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "technical_config"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

config.technical_config.version_created

A new version of the PSP's technical_config configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.technical_config.version_created/v1",
  "title": "config.technical_config.version_created",
  "description": "A new version of the PSP's technical_config configuration was created (a change or a rollback). Kinds that need approval stay pending until a second person approves.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None; an approver decides the pending approval when one is needed."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "const": "technical_config"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "needs_approval": {
      "type": "boolean"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    },
    "rollback_of": {
      "type": "integer",
      "description": "The version this one copies, for a rollback."
    }
  },
  "additionalProperties": false
}

credential.psp_key.registered

A PSP registered a public key (PSP-11.2): SSH for KulPay-hosted SFTP login, or PGP to verify its statement signatures. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.psp_key.registered/v1",
  "title": "credential.psp_key.registered",
  "description": "A PSP registered a public key (PSP-11.2): SSH for KulPay-hosted SFTP login, or PGP to verify its statement signatures.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "key_id",
    "kind",
    "fingerprint"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "key_id": {
      "type": "string"
    },
    "kind": {
      "type": "string",
      "enum": [
        "ssh",
        "pgp"
      ]
    },
    "fingerprint": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

credential.psp_key.revoked

A PSP revoked one of its SSH or PGP public keys (PSP-11.2); it stops working at once. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.psp_key.revoked/v1",
  "title": "credential.psp_key.revoked",
  "description": "A PSP revoked one of its SSH or PGP public keys (PSP-11.2); it stops working at once.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "key_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "key_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.applied.entered

A PSP entered the applied state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.applied.entered/v1",
  "title": "psp.applied.entered",
  "description": "A PSP entered the applied state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.certification.case_failed

A certification case failed: what was expected and what happened, with the payment it created. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.certification.case_failed/v1",
  "title": "psp.certification.case_failed",
  "description": "A certification case failed: what was expected and what happened, with the payment it created.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "The PSP fixes its integration and repeats the failed cases."
  },
  "type": "object",
  "required": [
    "psp_id",
    "run_id",
    "case"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "run_id": {
      "type": "string"
    },
    "case": {
      "type": "string"
    },
    "mandatory": {
      "type": "boolean"
    },
    "expected": {
      "type": "string"
    },
    "actual": {
      "type": "string"
    },
    "payment_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.certification.entered

A PSP entered the certification state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.certification.entered/v1",
  "title": "psp.certification.entered",
  "description": "A PSP entered the certification state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.certification.failed

A certification run finished with mandatory cases failed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.certification.failed/v1",
  "title": "psp.certification.failed",
  "description": "A certification run finished with mandatory cases failed.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "The PSP repeats the failed cases after fixing them."
  },
  "type": "object",
  "required": [
    "psp_id",
    "run_id",
    "failed_cases"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "run_id": {
      "type": "string"
    },
    "failed_cases": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "passed": {
      "type": "integer"
    },
    "failed": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.certification.passed

Every mandatory certification case passed. The report is kept with its SHA-256 and, when a signing key is configured, an Ed25519 signature. A PSP in technical_setup moves to certification. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.certification.passed/v1",
  "title": "psp.certification.passed",
  "description": "Every mandatory certification case passed. The report is kept with its SHA-256 and, when a signing key is configured, an Ed25519 signature. A PSP in technical_setup moves to certification.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "run_id",
    "report_sha256"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "run_id": {
      "type": "string"
    },
    "report_sha256": {
      "type": "string"
    },
    "signed": {
      "type": "boolean"
    },
    "signing_key_id": {
      "type": "string"
    },
    "passed": {
      "type": "integer"
    },
    "duration_seconds": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.certification.run_started

A certification run started in the PSP's sandbox: the suite generated from its capability matrix, or only the failed cases of an earlier run. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.certification.run_started/v1",
  "title": "psp.certification.run_started",
  "description": "A certification run started in the PSP's sandbox: the suite generated from its capability matrix, or only the failed cases of an earlier run.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None; the run reports psp.certification.passed or psp.certification.failed."
  },
  "type": "object",
  "required": [
    "psp_id",
    "run_id",
    "cases"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "run_id": {
      "type": "string"
    },
    "cases": {
      "type": "integer"
    },
    "mandatory": {
      "type": "integer"
    },
    "rerun_of": {
      "type": "string"
    },
    "capability_matrix_version": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.commercial_setup.entered

A PSP entered the commercial_setup state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.commercial_setup.entered/v1",
  "title": "psp.commercial_setup.entered",
  "description": "A PSP entered the commercial_setup state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.commercial_terms.approved

A second person approved a version of the PSP's commercial terms; it is now active. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.commercial_terms.approved/v1",
  "title": "psp.commercial_terms.approved",
  "description": "A second person approved a version of the PSP's commercial terms; it is now active.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "version",
    "version_id",
    "approved_by"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.commercial_terms.version_created

A new version of the PSP's commercial terms (fees, funds flow, settlement, limits) was prepared for approval. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.commercial_terms.version_created/v1",
  "title": "psp.commercial_terms.version_created",
  "description": "A new version of the PSP's commercial terms (fees, funds flow, settlement, limits) was prepared for approval.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "A second person approves or rejects it."
  },
  "type": "object",
  "required": [
    "psp_id",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.contract.signed

The signed contract for the PSP's active commercial terms was recorded. It is a pilot gate. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.contract.signed/v1",
  "title": "psp.contract.signed",
  "description": "The signed contract for the PSP's active commercial terms was recorded. It is a pilot gate.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "contract_ref",
    "signed_copy_file_id",
    "sha256"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "contract_ref": {
      "type": "string"
    },
    "signed_copy_file_id": {
      "type": "string"
    },
    "sha256": {
      "type": "string",
      "description": "SHA-256 of the signed copy; the file is stored encrypted."
    },
    "commercial_terms_version": {
      "type": "integer"
    },
    "recorded_by": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.go_live.approved

Two people approved go-live: the live gates were met, and the go-live checklist, with the signed certification report, was stored on the PSP. psp.live.entered follows. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.go_live.approved/v1",
  "title": "psp.go_live.approved",
  "description": "Two people approved go-live: the live gates were met, and the go-live checklist, with the signed certification report, was stored on the PSP. psp.live.entered follows.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "approval_id",
    "prepared_by",
    "approved_by",
    "checklist_sha256"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    },
    "checklist_sha256": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.health.degraded

A PSP's circuit for an operation opened (PSP-9.4): too many of its calls failed or ended unknown (by default 50% of at least 20 in 30 s). Routing skips the PSP for that operation; after the open period a few trial calls test it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.health.degraded/v1",
  "title": "psp.health.degraded",
  "description": "A PSP's circuit for an operation opened (PSP-9.4): too many of its calls failed or ended unknown (by default 50% of at least 20 in 30 s). Routing skips the PSP for that operation; after the open period a few trial calls test it.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Check the PSP's status page and contact; traffic is moving to other PSPs where routing allows."
  },
  "type": "object",
  "required": [
    "psp_id",
    "operation",
    "state",
    "from"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "operation": {
      "type": "string"
    },
    "state": {
      "type": "string"
    },
    "from": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.health.recovered

A PSP's circuit for an operation closed again after its trial calls succeeded (PSP-9.4); routing sends it traffic again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.health.recovered/v1",
  "title": "psp.health.recovered",
  "description": "A PSP's circuit for an operation closed again after its trial calls succeeded (PSP-9.4); routing sends it traffic again.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "operation",
    "state",
    "from"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "operation": {
      "type": "string"
    },
    "state": {
      "type": "string"
    },
    "from": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.incident.recorded

An incident was recorded against the PSP. A critical one during pilot holds go-live: the 7 pilot days count again from its resolution. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.incident.recorded/v1",
  "title": "psp.incident.recorded",
  "description": "An incident was recorded against the PSP. A critical one during pilot holds go-live: the 7 pilot days count again from its resolution.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Resolve it with POST /v1/admin/psps/{id}/incidents/{incident}/resolve."
  },
  "type": "object",
  "required": [
    "psp_id",
    "incident_id",
    "severity"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "incident_id": {
      "type": "string"
    },
    "severity": {
      "enum": [
        "critical",
        "major",
        "minor"
      ]
    },
    "summary": {
      "type": "string"
    },
    "status": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.incident.resolved

An incident against the PSP was resolved. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.incident.resolved/v1",
  "title": "psp.incident.resolved",
  "description": "An incident against the PSP was resolved.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "incident_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "incident_id": {
      "type": "string"
    },
    "severity": {
      "enum": [
        "critical",
        "major",
        "minor"
      ]
    },
    "resolution": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.info_requested.entered

A PSP entered the info_requested state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.info_requested.entered/v1",
  "title": "psp.info_requested.entered",
  "description": "A PSP entered the info_requested state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.institution_updated

An operator set the PSP's place in the payment system (IO-4.2): the bank code its NIBs start with and its mobile wallets' prefixes. KulPay infers by them the institution of a biller's credit account (to_psp), and a credit account no known PSP serves blocks the biller's go-live. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.institution_updated/v1",
  "title": "psp.institution_updated",
  "description": "An operator set the PSP's place in the payment system (IO-4.2): the bank code its NIBs start with and its mobile wallets' prefixes. KulPay infers by them the institution of a biller's credit account (to_psp), and a credit account no known PSP serves blocks the biller's go-live.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "bank_code",
    "wallet_prefixes",
    "from"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "bank_code": {
      "type": "string"
    },
    "wallet_prefixes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "from": {
      "type": "object",
      "properties": {
        "bank_code": {
          "type": "string"
        },
        "wallet_prefixes": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "wallet_method": {
          "type": "string"
        }
      }
    },
    "reason": {
      "type": "string"
    },
    "wallet_method": {
      "type": "string",
      "description": "The payout method its wallets are paid by (mpesa, emola), for remittances to them (IO-4.3)."
    }
  },
  "additionalProperties": false
}

psp.kyb.approved

KYB was approved by a second compliance officer (maker-checker). The 1-day clock to live starts. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.kyb.approved/v1",
  "title": "psp.kyb.approved",
  "description": "KYB was approved by a second compliance officer (maker-checker). The 1-day clock to live starts.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Set up commercial terms."
  },
  "type": "object",
  "required": [
    "psp_id",
    "prepared_by",
    "approved_by"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "risk_tier": {
      "type": "string"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.kyb.document.reviewed

A compliance officer accepted or rejected a KYB document. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.kyb.document.reviewed/v1",
  "title": "psp.kyb.document.reviewed",
  "description": "A compliance officer accepted or rejected a KYB document.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "document_id",
    "type",
    "decision"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "document_id": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "decision": {
      "enum": [
        "accepted",
        "rejected"
      ]
    },
    "comment": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.kyb.document.uploaded

A KYB document was uploaded, encrypted, with the checksum of its content. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.kyb.document.uploaded/v1",
  "title": "psp.kyb.document.uploaded",
  "description": "A KYB document was uploaded, encrypted, with the checksum of its content.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Review the document."
  },
  "type": "object",
  "required": [
    "psp_id",
    "document_id",
    "type",
    "sha256"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "document_id": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "file_id": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "filename": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.kyb.info_requested

The reviewer asked the PSP for more information. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.kyb.info_requested/v1",
  "title": "psp.kyb.info_requested",
  "description": "The reviewer asked the PSP for more information.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "The PSP provides what is missing."
  },
  "type": "object",
  "required": [
    "psp_id",
    "missing"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "missing": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.kyb.rejected

KYB was rejected. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.kyb.rejected/v1",
  "title": "psp.kyb.rejected",
  "description": "KYB was rejected.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Inform the applicant."
  },
  "type": "object",
  "required": [
    "psp_id",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.kyb_approved.entered

A PSP entered the kyb_approved state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.kyb_approved.entered/v1",
  "title": "psp.kyb_approved.entered",
  "description": "A PSP entered the kyb_approved state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.kyb_review.entered

A PSP entered the kyb_review state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.kyb_review.entered/v1",
  "title": "psp.kyb_review.entered",
  "description": "A PSP entered the kyb_review state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.live.entered

A PSP entered the live state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.live.entered/v1",
  "title": "psp.live.entered",
  "description": "A PSP entered the live state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.live_key.revealed

The PSP revealed its live key, issued at pilot. It was shown once and is no longer stored. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.live_key.revealed/v1",
  "title": "psp.live_key.revealed",
  "description": "The PSP revealed its live key, issued at pilot. It was shown once and is no longer stored.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "key_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "key_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.logo.approved

A partner's logo was published: an operator approved the partner's upload, or uploaded it (LOGO-1). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.logo.approved/v1",
  "title": "psp.logo.approved",
  "description": "A partner's logo was published: an operator approved the partner's upload, or uploaded it (LOGO-1).",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None: channels get the new logo URL from the biller catalogue."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "file_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "enum": [
        "psp",
        "lsp"
      ]
    },
    "file_id": {
      "type": "string"
    },
    "width": {
      "type": "integer"
    },
    "height": {
      "type": "integer"
    },
    "bytes": {
      "type": "integer"
    },
    "by_operator": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

psp.logo.rejected

An operator rejected a partner's logo, saying why; the previously approved logo, if any, stays published (LOGO-1). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.logo.rejected/v1",
  "title": "psp.logo.rejected",
  "description": "An operator rejected a partner's logo, saying why; the previously approved logo, if any, stays published (LOGO-1).",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "The partner uploads a corrected logo."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "file_id",
    "note"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "enum": [
        "psp",
        "lsp"
      ]
    },
    "file_id": {
      "type": "string"
    },
    "note": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.logo.submitted

A partner (PSP or biller) uploaded a logo that passed the checks (PNG, transparent edges, size) and waits for an operator's review (LOGO-1). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.logo.submitted/v1",
  "title": "psp.logo.submitted",
  "description": "A partner (PSP or biller) uploaded a logo that passed the checks (PNG, transparent edges, size) and waits for an operator's review (LOGO-1).",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Review the logo: approve it, or reject it saying why."
  },
  "type": "object",
  "required": [
    "psp_id",
    "kind",
    "file_id",
    "width",
    "height",
    "bytes",
    "by_operator"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "kind": {
      "enum": [
        "psp",
        "lsp"
      ]
    },
    "file_id": {
      "type": "string"
    },
    "width": {
      "type": "integer"
    },
    "height": {
      "type": "integer"
    },
    "bytes": {
      "type": "integer"
    },
    "by_operator": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

psp.offboarded

Offboarding finished: no open payments, reconciliation closed. Nothing was deleted. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.offboarded/v1",
  "title": "psp.offboarded",
  "description": "Offboarding finished: no open payments, reconciliation closed. Nothing was deleted.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": true,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.offboarded.entered

A PSP entered the offboarded state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.offboarded.entered/v1",
  "title": "psp.offboarded.entered",
  "description": "A PSP entered the offboarded state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.offboarding.entered

A PSP entered the offboarding state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.offboarding.entered/v1",
  "title": "psp.offboarding.entered",
  "description": "A PSP entered the offboarding state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.offboarding.started

Offboarding was approved by two people: the account takes no new payments and every key was revoked. The PSP waits for its payments to be final and its reconciliation closed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.offboarding.started/v1",
  "title": "psp.offboarding.started",
  "description": "Offboarding was approved by two people: the account takes no new payments and every key was revoked. The PSP waits for its payments to be final and its reconciliation closed.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Settle and reconcile the open items, then POST /v1/admin/psps/{id}/offboarding/complete."
  },
  "type": "object",
  "required": [
    "psp_id",
    "approval_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    },
    "keys_revoked": {
      "type": "integer"
    },
    "open_payments": {
      "type": "integer"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.pilot.entered

A PSP entered the pilot state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.pilot.entered/v1",
  "title": "psp.pilot.entered",
  "description": "A PSP entered the pilot state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.pilot.settings_changed

The pilot limits, merchant allow-list or routing weight changed. Limits never exceed the contracted ones. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.pilot.settings_changed/v1",
  "title": "psp.pilot.settings_changed",
  "description": "The pilot limits, merchant allow-list or routing weight changed. Limits never exceed the contracted ones.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "per_transaction": {
      "type": "string"
    },
    "daily": {
      "type": "string"
    },
    "monthly": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "routing_weight": {
      "type": "integer"
    },
    "merchants": {
      "type": "integer"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.pilot.setup_failed

The PSP entered pilot but its pilot settings or live key could not be set up. The move stands. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.pilot.setup_failed/v1",
  "title": "psp.pilot.setup_failed",
  "description": "The PSP entered pilot but its pilot settings or live key could not be set up. The move stands.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Retry POST /v1/admin/psps/{id}/pilot/setup after fixing the cause."
  },
  "type": "object",
  "required": [
    "psp_id",
    "error"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.pilot.started

The PSP entered pilot for the first time: pilot limits (10% of the contracted limits by default), an empty merchant allow-list, a routing weight, and a live key the PSP reveals once. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.pilot.started/v1",
  "title": "psp.pilot.started",
  "description": "The PSP entered pilot for the first time: pilot limits (10% of the contracted limits by default), an empty merchant allow-list, a routing weight, and a live key the PSP reveals once.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Add the pilot merchants to the allow-list."
  },
  "type": "object",
  "required": [
    "psp_id",
    "live_key_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "per_transaction": {
      "type": "string"
    },
    "daily": {
      "type": "string"
    },
    "monthly": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "routing_weight": {
      "type": "integer"
    },
    "live_key_id": {
      "type": "string"
    },
    "live_key_last4": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.profile.change_decided

A requested change of a PSP's registered details was approved (and applied) or rejected (PSP-13.3). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.profile.change_decided/v1",
  "title": "psp.profile.change_decided",
  "description": "A requested change of a PSP's registered details was approved (and applied) or rejected (PSP-13.3).",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "approval_id",
    "decision"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    },
    "decision": {
      "enum": [
        "approved",
        "rejected"
      ]
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.profile.change_requested

A PSP admin asked KulPay to change the PSP's registered details (legal name, registration number) in the portal (PSP-13.3); a compliance officer approves or rejects it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.profile.change_requested/v1",
  "title": "psp.profile.change_requested",
  "description": "A PSP admin asked KulPay to change the PSP's registered details (legal name, registration number) in the portal (PSP-13.3); a compliance officer approves or rejects it.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "A compliance officer checks the change against the PSP's registration documents and decides the approval request on the PSP's page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "approval_id",
    "requested_by"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    },
    "requested_by": {
      "type": "string"
    },
    "legal_name": {
      "type": "string"
    },
    "registration_number": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.profile.contacts_updated

A PSP admin changed the PSP's technical or compliance contact in the portal (PSP-13.3). The contacts themselves are not recorded in the event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.profile.contacts_updated/v1",
  "title": "psp.profile.contacts_updated",
  "description": "A PSP admin changed the PSP's technical or compliance contact in the portal (PSP-13.3). The contacts themselves are not recorded in the event.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "updated_by"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "updated_by": {
      "type": "string",
      "description": "The portal user."
    }
  },
  "additionalProperties": false
}

psp.reinstated

A suspended PSP was reinstated to the state it left; its account and keys work again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.reinstated/v1",
  "title": "psp.reinstated",
  "description": "A suspended PSP was reinstated to the state it left; its account and keys work again.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.rejected.entered

A PSP entered the rejected state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.rejected.entered/v1",
  "title": "psp.rejected.entered",
  "description": "A PSP entered the rejected state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.review.completed

A periodic review was completed; the next one is scheduled by risk tier. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.review.completed/v1",
  "title": "psp.review.completed",
  "description": "A periodic review was completed; the next one is scheduled by risk tier.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None, unless the outcome asks for actions."
  },
  "type": "object",
  "required": [
    "psp_id",
    "outcome",
    "next_review_at"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "outcome": {
      "enum": [
        "satisfactory",
        "actions_required",
        "escalated"
      ]
    },
    "notes": {
      "type": "string"
    },
    "reviewer": {
      "type": "string"
    },
    "risk_tier": {
      "type": "string"
    },
    "next_review_at": {
      "type": "string"
    },
    "was_due_at": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.review.due

A PSP's periodic review is due within 30 days, or (overdue: true) is past due. Reviews fall due by risk tier: high every 6 months, medium 12, low 24. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.review.due/v1",
  "title": "psp.review.due",
  "description": "A PSP's periodic review is due within 30 days, or (overdue: true) is past due. Reviews fall due by risk tier: high every 6 months, medium 12, low 24.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Complete the review: POST /v1/admin/psps/{id}/reviews."
  },
  "type": "object",
  "required": [
    "psp_id",
    "due_at",
    "overdue"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "risk_tier": {
      "type": "string"
    },
    "due_at": {
      "type": "string"
    },
    "overdue": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

psp.risk_tier.set

The reviewer set the PSP's risk tier, which sets its default limits and review frequency. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.risk_tier.set/v1",
  "title": "psp.risk_tier.set",
  "description": "The reviewer set the PSP's risk tier, which sets its default limits and review frequency.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "tier"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "tier": {
      "enum": [
        "low",
        "medium",
        "high"
      ]
    },
    "previous_tier": {
      "type": "string"
    },
    "review_months": {
      "type": "integer"
    },
    "next_review_at": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.sandbox.enable_failed

The PSP entered technical setup but its sandbox could not be opened. The move stands; an operator retries opening the sandbox. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.sandbox.enable_failed/v1",
  "title": "psp.sandbox.enable_failed",
  "description": "The PSP entered technical setup but its sandbox could not be opened. The move stands; an operator retries opening the sandbox.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Retry POST /v1/admin/psps/{id}/sandbox after fixing the cause."
  },
  "type": "object",
  "required": [
    "psp_id",
    "error"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.sandbox.enabled

The PSP entered technical setup and its sandbox opened: a test-mode key (shown once to the PSP) and test-mode mock connectors for every method. Live keys stay unavailable until pilot. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.sandbox.enabled/v1",
  "title": "psp.sandbox.enabled",
  "description": "The PSP entered technical setup and its sandbox opened: a test-mode key (shown once to the PSP) and test-mode mock connectors for every method. Live keys stay unavailable until pilot.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "key_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "key_id": {
      "type": "string"
    },
    "key_last4": {
      "type": "string"
    },
    "methods": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "connector": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.sandbox.key_revealed

The PSP revealed its sandbox test key. It was shown once and is no longer stored. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.sandbox.key_revealed/v1",
  "title": "psp.sandbox.key_revealed",
  "description": "The PSP revealed its sandbox test key. It was shown once and is no longer stored.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "key_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "key_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.screening.completed

A sanctions or PEP screening was performed and its result stored as evidence. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.screening.completed/v1",
  "title": "psp.screening.completed",
  "description": "A sanctions or PEP screening was performed and its result stored as evidence.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "A hit or a manual review required needs a decision."
  },
  "type": "object",
  "required": [
    "psp_id",
    "screening_id",
    "kind",
    "subject",
    "provider",
    "result"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "screening_id": {
      "type": "string"
    },
    "kind": {
      "enum": [
        "sanctions",
        "pep"
      ]
    },
    "subject": {
      "type": "string"
    },
    "provider": {
      "type": "string"
    },
    "result": {
      "type": "string"
    },
    "reference": {
      "type": "string"
    },
    "evidence_file_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.screening.hit

A screening matched a sanctions or PEP list. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.screening.hit/v1",
  "title": "psp.screening.hit",
  "description": "A screening matched a sanctions or PEP list.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Decide whether the match is true before KYB proceeds."
  },
  "type": "object",
  "required": [
    "psp_id",
    "screening_id",
    "kind",
    "subject",
    "provider"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "screening_id": {
      "type": "string"
    },
    "kind": {
      "type": "string"
    },
    "subject": {
      "type": "string"
    },
    "provider": {
      "type": "string"
    },
    "matches": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

psp.suspended

The PSP was suspended: its account takes no new payments and its keys are refused on every node from the next request; payments in flight run to their end. Approved by two people, or an emergency suspension by one officer that a second must review within 24 hours. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.suspended/v1",
  "title": "psp.suspended",
  "description": "The PSP was suspended: its account takes no new payments and its keys are refused on every node from the next request; payments in flight run to their end. Approved by two people, or an emergency suspension by one officer that a second must review within 24 hours.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "An emergency suspension: review it (approval psp.suspension.review)."
  },
  "type": "object",
  "required": [
    "psp_id",
    "from",
    "emergency"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "emergency": {
      "type": "boolean"
    },
    "approval_id": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.suspended.entered

A PSP entered the suspended state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.suspended.entered/v1",
  "title": "psp.suspended.entered",
  "description": "A PSP entered the suspended state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.suspension.review_overdue

An emergency suspension has not been reviewed within 24 hours. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.suspension.review_overdue/v1",
  "title": "psp.suspension.review_overdue",
  "description": "An emergency suspension has not been reviewed within 24 hours.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "A second officer reviews it now."
  },
  "type": "object",
  "required": [
    "psp_id",
    "approval_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    },
    "suspended_at": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.suspension.reviewed

A second officer reviewed an emergency suspension: upheld, or overturned (the PSP is reinstated). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.suspension.reviewed/v1",
  "title": "psp.suspension.reviewed",
  "description": "A second officer reviewed an emergency suspension: upheld, or overturned (the PSP is reinstated).",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "approval_id",
    "upheld"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    },
    "upheld": {
      "type": "boolean"
    },
    "reviewer": {
      "type": "string"
    },
    "suspended_by": {
      "type": "string"
    },
    "late": {
      "type": "boolean"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.technical_config.version_created

A new version of the PSP's technical configuration (endpoints, credentials, webhooks, IP allow-list) was created. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.technical_config.version_created/v1",
  "title": "psp.technical_config.version_created",
  "description": "A new version of the PSP's technical configuration (endpoints, credentials, webhooks, IP allow-list) was created.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "version",
    "version_id"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp.technical_setup.entered

A PSP entered the technical_setup state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.technical_setup.entered/v1",
  "title": "psp.technical_setup.entered",
  "description": "A PSP entered the technical_setup state.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Audit record of the lifecycle; see the PSP page."
  },
  "type": "object",
  "required": [
    "psp_id",
    "to",
    "actor",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string",
      "maxLength": 500
    },
    "seconds_in_previous_state": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.technical_step.probed

A setup step's endpoint was dialled over TLS in the background, through the SSRF guard, and the result recorded on the step. A failed probe fails the step until the endpoint is fixed and saved again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.technical_step.probed/v1",
  "title": "psp.technical_step.probed",
  "description": "A setup step's endpoint was dialled over TLS in the background, through the SSRF guard, and the result recorded on the step. A failed probe fails the step until the endpoint is fixed and saved again.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "If the step failed, fix the endpoint (public address, TLS 1.2+, valid certificate) and save the step again."
  },
  "type": "object",
  "required": [
    "psp_id",
    "step",
    "status"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "step": {
      "enum": [
        "endpoints",
        "webhooks"
      ]
    },
    "status": {
      "enum": [
        "passed",
        "warning",
        "failed"
      ]
    },
    "failed_checks": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

psp.technical_step.validated

A technical setup step was validated. A failed step is not saved; the PSP sees which checks failed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.technical_step.validated/v1",
  "title": "psp.technical_step.validated",
  "description": "A technical setup step was validated. A failed step is not saved; the PSP sees which checks failed.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "step",
    "status"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "step": {
      "enum": [
        "capability_matrix",
        "orientation_profile",
        "endpoints",
        "credentials",
        "webhooks",
        "ip_allowlist",
        "statement_profile"
      ]
    },
    "status": {
      "enum": [
        "passed",
        "warning",
        "failed"
      ]
    },
    "failed_checks": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "config_version": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

psp.transition.rejected

A requested lifecycle transition was refused: it is not in the declared table or a gate is not met. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.transition.rejected/v1",
  "title": "psp.transition.rejected",
  "description": "A requested lifecycle transition was refused: it is not in the declared table or a gate is not met.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None; a control did its job."
  },
  "type": "object",
  "required": [
    "psp_id",
    "from",
    "to",
    "actor",
    "cause"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "cause": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

retry.budget_exceeded

Retries to a PSP reached its budget, by default 10% of its requests over a rolling minute with a floor of 10 (PSP-9.6). Further retries wait for the next run; none is dropped. Status queries for unknown outcomes are never held back. Recorded once a minute per PSP. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/retry.budget_exceeded/v1",
  "title": "retry.budget_exceeded",
  "description": "Retries to a PSP reached its budget, by default 10% of its requests over a rolling minute with a floor of 10 (PSP-9.6). Further retries wait for the next run; none is dropped. Status queries for unknown outcomes are never held back. Recorded once a minute per PSP.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "None while it passes; persistent, the PSP is struggling: check its circuits and status."
  },
  "type": "object",
  "required": [
    "psp_id",
    "operation",
    "retries",
    "requests",
    "ratio",
    "min_per_minute",
    "window_seconds"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "operation": {
      "type": "string"
    },
    "retries": {
      "type": "integer"
    },
    "requests": {
      "type": "integer"
    },
    "ratio": {
      "type": "number"
    },
    "min_per_minute": {
      "type": "integer"
    },
    "window_seconds": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

connector events

EventWebhookTerminalFires whenWhat to do
config.connector.configured v1 no no A connector binding was created or changed: driver, credentials or enabled state. Audit record. Compare before and after.
config.connector.removed v1 no no A connector binding was removed; the method falls back to the platform default. Audit record. Compare before and after.

config.connector.configured

A connector binding was created or changed: driver, credentials or enabled state. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.connector.configured/v1",
  "title": "config.connector.configured",
  "description": "A connector binding was created or changed: driver, credentials or enabled state.",
  "x-kulpay": {
    "aggregate": "connector",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/connector.json"
    },
    "after": {
      "$ref": "kulpay://objects/connector.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

config.connector.removed

A connector binding was removed; the method falls back to the platform default. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.connector.removed/v1",
  "title": "config.connector.removed",
  "description": "A connector binding was removed; the method falls back to the platform default.",
  "x-kulpay": {
    "aggregate": "connector",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "before"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/connector.json"
    },
    "after": {
      "$ref": "kulpay://objects/connector.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

data_transfer_register events

EventWebhookTerminalFires whenWhat to do
config.data_transfer_register.saved v1 no no An entry of the cross-border transfer register was listed, updated (legal basis, data processing agreement) or reviewed (PSP-12.3). None.
data_transfer.register.review_due v1 no no An entry of the cross-border transfer register is due its yearly review (PSP-12.3): its legal basis and data processing agreement are to be confirmed. Announced once per cycle. Compliance reviews the entry on /admin/data-transfers and records the review, which sets the next one a year on.

config.data_transfer_register.saved

An entry of the cross-border transfer register was listed, updated (legal basis, data processing agreement) or reviewed (PSP-12.3). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.data_transfer_register.saved/v1",
  "title": "config.data_transfer_register.saved",
  "description": "An entry of the cross-border transfer register was listed, updated (legal basis, data processing agreement) or reviewed (PSP-12.3).",
  "x-kulpay": {
    "aggregate": "data_transfer_register",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "connector",
    "legal_entity",
    "country",
    "change"
  ],
  "properties": {
    "connector": {
      "type": "string"
    },
    "legal_entity": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "change": {
      "enum": [
        "listed",
        "updated",
        "reviewed"
      ]
    },
    "next_review_at": {
      "type": "string",
      "format": "date-time"
    },
    "actor": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

data_transfer.register.review_due

An entry of the cross-border transfer register is due its yearly review (PSP-12.3): its legal basis and data processing agreement are to be confirmed. Announced once per cycle. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/data_transfer.register.review_due/v1",
  "title": "data_transfer.register.review_due",
  "description": "An entry of the cross-border transfer register is due its yearly review (PSP-12.3): its legal basis and data processing agreement are to be confirmed. Announced once per cycle.",
  "x-kulpay": {
    "aggregate": "data_transfer_register",
    "webhook": false,
    "terminal": false,
    "act": "Compliance reviews the entry on /admin/data-transfers and records the review, which sets the next one a year on."
  },
  "type": "object",
  "required": [
    "connector",
    "legal_entity",
    "country",
    "due_at"
  ],
  "properties": {
    "connector": {
      "type": "string"
    },
    "legal_entity": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "due_at": {
      "type": "string",
      "format": "date-time"
    },
    "last_reviewed_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

fee_schedule events

EventWebhookTerminalFires whenWhat to do
config.fee_schedule.activated v1 no no A fee schedule version was approved and is active (PSP-10.5), superseding the previous one. Fees are worked out at posting time with the version in force, and each payment records the version it used. None: transactions from now on are priced with this version.
config.fee_schedule.version_created v1 no no A new version of a PSP's or merchant's fee schedule was proposed (PSP-10.5). It waits for a second person; until then the active version keeps applying. A checker reviews the rules and approves or rejects the version.

config.fee_schedule.activated

A fee schedule version was approved and is active (PSP-10.5), superseding the previous one. Fees are worked out at posting time with the version in force, and each payment records the version it used. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.fee_schedule.activated/v1",
  "title": "config.fee_schedule.activated",
  "description": "A fee schedule version was approved and is active (PSP-10.5), superseding the previous one. Fees are worked out at posting time with the version in force, and each payment records the version it used.",
  "x-kulpay": {
    "aggregate": "fee_schedule",
    "webhook": false,
    "terminal": false,
    "act": "None: transactions from now on are priced with this version."
  },
  "type": "object",
  "required": [
    "schedule_id",
    "scope",
    "subject_id",
    "version",
    "previous_version"
  ],
  "properties": {
    "schedule_id": {
      "type": "string"
    },
    "scope": {
      "type": "string",
      "enum": [
        "psp",
        "merchant"
      ]
    },
    "subject_id": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "approval_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

config.fee_schedule.version_created

A new version of a PSP's or merchant's fee schedule was proposed (PSP-10.5). It waits for a second person; until then the active version keeps applying. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.fee_schedule.version_created/v1",
  "title": "config.fee_schedule.version_created",
  "description": "A new version of a PSP's or merchant's fee schedule was proposed (PSP-10.5). It waits for a second person; until then the active version keeps applying.",
  "x-kulpay": {
    "aggregate": "fee_schedule",
    "webhook": false,
    "terminal": false,
    "act": "A checker reviews the rules and approves or rejects the version."
  },
  "type": "object",
  "required": [
    "schedule_id",
    "scope",
    "subject_id",
    "version"
  ],
  "properties": {
    "schedule_id": {
      "type": "string"
    },
    "scope": {
      "type": "string",
      "enum": [
        "psp",
        "merchant"
      ]
    },
    "subject_id": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "approval_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

funds_flow events

EventWebhookTerminalFires whenWhat to do
config.funds_flow.changed v1 no no A funds-flow model changed (PSP-10.2): a PSP's default, through its approved commercial terms (scope psp), or a merchant's override, through maker-checker approval (scope merchant; an empty `to` follows the PSP again). collecting: PSP to KulPay's safeguarding account to the merchant; direct: PSP to the merchant. Only payments created afterwards use the new model. Settlement and reconciliation follow the new money path for new payments; payments already created keep theirs.

config.funds_flow.changed

A funds-flow model changed (PSP-10.2): a PSP's default, through its approved commercial terms (scope psp), or a merchant's override, through maker-checker approval (scope merchant; an empty `to` follows the PSP again). collecting: PSP to KulPay's safeguarding account to the merchant; direct: PSP to the merchant. Only payments created afterwards use the new model. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.funds_flow.changed/v1",
  "title": "config.funds_flow.changed",
  "description": "A funds-flow model changed (PSP-10.2): a PSP's default, through its approved commercial terms (scope psp), or a merchant's override, through maker-checker approval (scope merchant; an empty `to` follows the PSP again). collecting: PSP to KulPay's safeguarding account to the merchant; direct: PSP to the merchant. Only payments created afterwards use the new model.",
  "x-kulpay": {
    "aggregate": "funds_flow",
    "webhook": false,
    "terminal": false,
    "act": "Settlement and reconciliation follow the new money path for new payments; payments already created keep theirs."
  },
  "type": "object",
  "required": [
    "scope",
    "subject_id",
    "from",
    "to"
  ],
  "properties": {
    "scope": {
      "type": "string",
      "enum": [
        "psp",
        "merchant"
      ]
    },
    "subject_id": {
      "type": "string"
    },
    "from": {
      "type": "string",
      "enum": [
        "",
        "collecting",
        "direct"
      ]
    },
    "to": {
      "type": "string",
      "enum": [
        "",
        "collecting",
        "direct"
      ]
    },
    "version": {
      "type": "integer"
    },
    "approval_id": {
      "type": "string"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

regulatory_template events

EventWebhookTerminalFires whenWhat to do
config.regulatory_template.saved v1 no no A regulatory report template was created or given a new version (PSP-10.6): source, fields, grouping, period and format. None.

config.regulatory_template.saved

A regulatory report template was created or given a new version (PSP-10.6): source, fields, grouping, period and format. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.regulatory_template.saved/v1",
  "title": "config.regulatory_template.saved",
  "description": "A regulatory report template was created or given a new version (PSP-10.6): source, fields, grouping, period and format.",
  "x-kulpay": {
    "aggregate": "regulatory_template",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "template_id",
    "code",
    "version",
    "source",
    "period",
    "format"
  ],
  "properties": {
    "template_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "source": {
      "type": "string"
    },
    "period": {
      "type": "string"
    },
    "format": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

safeguarding_account events

EventWebhookTerminalFires whenWhat to do
config.safeguarding_account.added v1 no no A bank account holding client funds was registered (PSP-10.4); its statements feed the daily safeguarding check. None.

config.safeguarding_account.added

A bank account holding client funds was registered (PSP-10.4); its statements feed the daily safeguarding check. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.safeguarding_account.added/v1",
  "title": "config.safeguarding_account.added",
  "description": "A bank account holding client funds was registered (PSP-10.4); its statements feed the daily safeguarding check.",
  "x-kulpay": {
    "aggregate": "safeguarding_account",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "account",
    "currency"
  ],
  "properties": {
    "account": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "bank": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

webhook_endpoint events

EventWebhookTerminalFires whenWhat to do
config.webhook_endpoint.registered v1 no no A webhook endpoint was registered. Audit record. Compare before and after.
config.webhook_endpoint.removed v1 no no A webhook endpoint was removed. Audit record. Compare before and after.
credential.webhook_secret.rotated v1 no no A webhook endpoint's signing secret was rolled (PSP-8.8): the old secret keeps signing until previous_expires_at, so every delivery carries a v1= for both secrets meanwhile. Deploy the new secret to the receiver before previous_expires_at.
delivery.replayed v1 no no Deliveries to an endpoint were queued again (PSP-9.5): one delivery, the dead-lettered ones, or every event since one; each event once. None.
webhook_endpoint.contacts_set v1 no no The contacts alerted about an endpoint's dead-lettered deliveries and disabling were set (PSP-9.5). The addresses themselves are not recorded here. None.
webhook_endpoint.disabled v1 no no KulPay disabled a webhook endpoint (PSP-9.5): it answered 410 Gone, or its deliveries were dead-lettered three days running. Its contacts are told; nothing is sent to it until it is enabled again. The receiver fixes the endpoint, enables it and replays what it missed.
webhook_endpoint.enabled v1 no no A disabled webhook endpoint was enabled again (PSP-9.5). Replay what it missed.
webhook_endpoint.paused v1 no no An endpoint was paused by its owner (PSP-7.5): nothing is sent to it until it is resumed, then what it missed is replayed from an event. None.
webhook_endpoint.schema_pinned v1 no no An endpoint pinned event types to schema versions (F3-18): each type is delivered in its version while it is supported; an empty map unpins every type. None.
webhook_endpoint.schema_retiring v1 no no A schema version an endpoint pins retires (F3-18): stage notice, 90 days before, its contacts emailed; stage retired, on the date, the pin dropped and the type delivered in its current version. Move to the current version before the date.
webhook_endpoint.signing_set v1 no no A webhook endpoint's Ed25519 signature (v1e=) was turned on or off (PSP-8.8). None.
webhook_endpoint.subscribed v1 no no An endpoint's subscription was set (PSP-7.5): the event types it receives, exact names or families ("payout.*"); empty is every webhook event. None.
webhook_endpoint.tested v1 no no A test event sent to one endpoint on request (PSP-7.5), through the normal signing and delivery path; no other endpoint receives it. None.

config.webhook_endpoint.registered

A webhook endpoint was registered. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.webhook_endpoint.registered/v1",
  "title": "config.webhook_endpoint.registered",
  "description": "A webhook endpoint was registered.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/webhook_endpoint.json"
    },
    "after": {
      "$ref": "kulpay://objects/webhook_endpoint.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

config.webhook_endpoint.removed

A webhook endpoint was removed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.webhook_endpoint.removed/v1",
  "title": "config.webhook_endpoint.removed",
  "description": "A webhook endpoint was removed.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/webhook_endpoint.json"
    },
    "after": {
      "$ref": "kulpay://objects/webhook_endpoint.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

credential.webhook_secret.rotated

A webhook endpoint's signing secret was rolled (PSP-8.8): the old secret keeps signing until previous_expires_at, so every delivery carries a v1= for both secrets meanwhile. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.webhook_secret.rotated/v1",
  "title": "credential.webhook_secret.rotated",
  "description": "A webhook endpoint's signing secret was rolled (PSP-8.8): the old secret keeps signing until previous_expires_at, so every delivery carries a v1= for both secrets meanwhile.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "Deploy the new secret to the receiver before previous_expires_at."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "previous_expires_at",
    "grace_seconds"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "previous_expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "grace_seconds": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

delivery.replayed

Deliveries to an endpoint were queued again (PSP-9.5): one delivery, the dead-lettered ones, or every event since one; each event once. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/delivery.replayed/v1",
  "title": "delivery.replayed",
  "description": "Deliveries to an endpoint were queued again (PSP-9.5): one delivery, the dead-lettered ones, or every event since one; each event once.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "count"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "since_event_id": {
      "type": "string"
    },
    "status": {
      "type": "string"
    },
    "delivery_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

webhook_endpoint.contacts_set

The contacts alerted about an endpoint's dead-lettered deliveries and disabling were set (PSP-9.5). The addresses themselves are not recorded here. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/webhook_endpoint.contacts_set/v1",
  "title": "webhook_endpoint.contacts_set",
  "description": "The contacts alerted about an endpoint's dead-lettered deliveries and disabling were set (PSP-9.5). The addresses themselves are not recorded here.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "contacts"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "contacts": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

webhook_endpoint.disabled

KulPay disabled a webhook endpoint (PSP-9.5): it answered 410 Gone, or its deliveries were dead-lettered three days running. Its contacts are told; nothing is sent to it until it is enabled again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/webhook_endpoint.disabled/v1",
  "title": "webhook_endpoint.disabled",
  "description": "KulPay disabled a webhook endpoint (PSP-9.5): it answered 410 Gone, or its deliveries were dead-lettered three days running. Its contacts are told; nothing is sent to it until it is enabled again.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "The receiver fixes the endpoint, enables it and replays what it missed."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "url",
    "reason"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "url": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "gone",
        "dead_letters"
      ]
    }
  },
  "additionalProperties": false
}

webhook_endpoint.enabled

A disabled webhook endpoint was enabled again (PSP-9.5). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/webhook_endpoint.enabled/v1",
  "title": "webhook_endpoint.enabled",
  "description": "A disabled webhook endpoint was enabled again (PSP-9.5).",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "Replay what it missed."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "url"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "url": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

webhook_endpoint.paused

An endpoint was paused by its owner (PSP-7.5): nothing is sent to it until it is resumed, then what it missed is replayed from an event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/webhook_endpoint.paused/v1",
  "title": "webhook_endpoint.paused",
  "description": "An endpoint was paused by its owner (PSP-7.5): nothing is sent to it until it is resumed, then what it missed is replayed from an event.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "url"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "url": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

webhook_endpoint.schema_pinned

An endpoint pinned event types to schema versions (F3-18): each type is delivered in its version while it is supported; an empty map unpins every type. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/webhook_endpoint.schema_pinned/v1",
  "title": "webhook_endpoint.schema_pinned",
  "description": "An endpoint pinned event types to schema versions (F3-18): each type is delivered in its version while it is supported; an empty map unpins every type.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "schema_versions"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "schema_versions": {
      "type": "object",
      "additionalProperties": {
        "type": "integer",
        "minimum": 1
      }
    }
  },
  "additionalProperties": false
}

webhook_endpoint.schema_retiring

A schema version an endpoint pins retires (F3-18): stage notice, 90 days before, its contacts emailed; stage retired, on the date, the pin dropped and the type delivered in its current version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/webhook_endpoint.schema_retiring/v1",
  "title": "webhook_endpoint.schema_retiring",
  "description": "A schema version an endpoint pins retires (F3-18): stage notice, 90 days before, its contacts emailed; stage retired, on the date, the pin dropped and the type delivered in its current version.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "Move to the current version before the date."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "event_type",
    "schema_version",
    "retires_at",
    "stage"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "event_type": {
      "type": "string"
    },
    "schema_version": {
      "type": "integer"
    },
    "retires_at": {
      "type": "string",
      "format": "date"
    },
    "stage": {
      "enum": [
        "notice",
        "retired"
      ]
    }
  },
  "additionalProperties": false
}

webhook_endpoint.signing_set

A webhook endpoint's Ed25519 signature (v1e=) was turned on or off (PSP-8.8). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/webhook_endpoint.signing_set/v1",
  "title": "webhook_endpoint.signing_set",
  "description": "A webhook endpoint's Ed25519 signature (v1e=) was turned on or off (PSP-8.8).",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "ed25519"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "ed25519": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

webhook_endpoint.subscribed

An endpoint's subscription was set (PSP-7.5): the event types it receives, exact names or families ("payout.*"); empty is every webhook event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/webhook_endpoint.subscribed/v1",
  "title": "webhook_endpoint.subscribed",
  "description": "An endpoint's subscription was set (PSP-7.5): the event types it receives, exact names or families (\"payout.*\"); empty is every webhook event.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "event_types"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "event_types": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

webhook_endpoint.tested

A test event sent to one endpoint on request (PSP-7.5), through the normal signing and delivery path; no other endpoint receives it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/webhook_endpoint.tested/v1",
  "title": "webhook_endpoint.tested",
  "description": "A test event sent to one endpoint on request (PSP-7.5), through the normal signing and delivery path; no other endpoint receives it.",
  "x-kulpay": {
    "aggregate": "webhook_endpoint",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "endpoint_id",
    "url",
    "message"
  ],
  "properties": {
    "endpoint_id": {
      "type": "string"
    },
    "url": {
      "type": "string"
    },
    "message": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

consumer events

EventWebhookTerminalFires whenWhat to do
consumer.dead_lettered v1 no no A consumer gave up on an event after its maximum attempts and moved it to the dead-letter queue. Operators: inspect the error and replay from the admin console.

consumer.dead_lettered

A consumer gave up on an event after its maximum attempts and moved it to the dead-letter queue. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/consumer.dead_lettered/v1",
  "title": "consumer.dead_lettered",
  "description": "A consumer gave up on an event after its maximum attempts and moved it to the dead-letter queue.",
  "x-kulpay": {
    "aggregate": "consumer",
    "webhook": false,
    "terminal": false,
    "act": "Operators: inspect the error and replay from the admin console."
  },
  "type": "object",
  "required": [
    "consumer",
    "event_id",
    "event_type",
    "attempts",
    "error"
  ],
  "properties": {
    "consumer": {
      "type": "string",
      "minLength": 1
    },
    "event_id": {
      "type": "string",
      "minLength": 1
    },
    "event_type": {
      "type": "string",
      "minLength": 1
    },
    "attempts": {
      "type": "integer",
      "minimum": 1
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

api_key events

EventWebhookTerminalFires whenWhat to do
credential.api_key.created v1 no no An API key was issued. Audit record. Compare before and after.
credential.api_key.expired v1 no yes An API key reached its expiry and stopped working (PSP-8.3): the end of a roll's grace period, or a set expiry. None, if every consumer moved to its successor; requests still made with it are refused with 401.
credential.api_key.expiring v1 no no An API key expires soon (PSP-8.4): reminders 30, 7 and 1 days before, also emailed to the account's technical and compliance contacts. Roll the key before it expires (POST /v1/api-keys/{id}/roll).
credential.api_key.leaked v1 no no A secret scanner (GitHub) found a real KulPay API key in public and reported it (PSP-8.6). A live key is revoked at once; a test key is revoked only when the deployment says so (secret_scanning.revoke_test_keys). The account's contacts are emailed. Issue a new key and deploy it; remove the leaked one from the repository and its history.
credential.api_key.location_baselined v1 no no An API key was used for the first time; its location is the baseline later uses are compared with (PSP-8.5). None.
credential.api_key.restricted v1 no no An API key's expiry or restrictions changed (PSP-8.4): the IP allow-list (its number of entries; the addresses are not recorded here), the per-transaction and daily amount ceilings and their currency. They hold on every node from the next request. Audit record.
credential.api_key.revoked v1 no no An API key was revoked. Audit record. Compare before and after.
credential.api_key.rotated v1 no no An API key was rolled (PSP-8.3): a new key with the same scopes was issued, and the old one keeps working until expires_at, the end of the grace period. Deploy the new key everywhere before expires_at; the old key's use count shows whether anything still uses it.
credential.blocked_ip v1 no no A call with an API key was refused with 403 ip_not_allowed: it came from an address outside the key's allow-list (PSP-8.4). Recorded at most once per key and address every ten minutes. Check whether the key leaked; revoke it if the address is not the holder's own.
credential.usage_spike v1 no no An API key made more than 5 times its 7-day average of requests today (with a baseline of at least 10 a day) (PSP-8.5). Recorded once a day. Confirm the traffic is the holder's (a launch, a batch job); otherwise revoke the key.
credential.used_from_new_location v1 no no An API key was used from a location it had not been used from before (PSP-8.5): a new country or ASN where the edge reports them, else a new network (IPv4 /16, IPv6 /32). Confirm with the key's holder; if they did not move, revoke the key.

credential.api_key.created

An API key was issued. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.api_key.created/v1",
  "title": "credential.api_key.created",
  "description": "An API key was issued.",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/api_key.json"
    },
    "after": {
      "$ref": "kulpay://objects/api_key.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

credential.api_key.expired

An API key reached its expiry and stopped working (PSP-8.3): the end of a roll's grace period, or a set expiry. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.api_key.expired/v1",
  "title": "credential.api_key.expired",
  "description": "An API key reached its expiry and stopped working (PSP-8.3): the end of a roll's grace period, or a set expiry.",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": true,
    "act": "None, if every consumer moved to its successor; requests still made with it are refused with 401."
  },
  "type": "object",
  "required": [
    "api_key_id",
    "use_count"
  ],
  "properties": {
    "api_key_id": {
      "type": "string"
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "use_count": {
      "type": "integer"
    },
    "rolled_to": {
      "type": "string"
    },
    "last_used_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

credential.api_key.expiring

An API key expires soon (PSP-8.4): reminders 30, 7 and 1 days before, also emailed to the account's technical and compliance contacts. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.api_key.expiring/v1",
  "title": "credential.api_key.expiring",
  "description": "An API key expires soon (PSP-8.4): reminders 30, 7 and 1 days before, also emailed to the account's technical and compliance contacts.",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "Roll the key before it expires (POST /v1/api-keys/{id}/roll)."
  },
  "type": "object",
  "required": [
    "api_key_id",
    "expires_at",
    "days_left"
  ],
  "properties": {
    "api_key_id": {
      "type": "string"
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "days_left": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

credential.api_key.leaked

A secret scanner (GitHub) found a real KulPay API key in public and reported it (PSP-8.6). A live key is revoked at once; a test key is revoked only when the deployment says so (secret_scanning.revoke_test_keys). The account's contacts are emailed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.api_key.leaked/v1",
  "title": "credential.api_key.leaked",
  "description": "A secret scanner (GitHub) found a real KulPay API key in public and reported it (PSP-8.6). A live key is revoked at once; a test key is revoked only when the deployment says so (secret_scanning.revoke_test_keys). The account's contacts are emailed.",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "Issue a new key and deploy it; remove the leaked one from the repository and its history."
  },
  "type": "object",
  "required": [
    "api_key_id",
    "source",
    "revoked",
    "already_revoked"
  ],
  "properties": {
    "api_key_id": {
      "type": "string"
    },
    "source": {
      "type": "string"
    },
    "url": {
      "type": "string"
    },
    "revoked": {
      "type": "boolean"
    },
    "already_revoked": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

credential.api_key.location_baselined

An API key was used for the first time; its location is the baseline later uses are compared with (PSP-8.5). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.api_key.location_baselined/v1",
  "title": "credential.api_key.location_baselined",
  "description": "An API key was used for the first time; its location is the baseline later uses are compared with (PSP-8.5).",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "api_key_id",
    "location"
  ],
  "properties": {
    "api_key_id": {
      "type": "string"
    },
    "location": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

credential.api_key.restricted

An API key's expiry or restrictions changed (PSP-8.4): the IP allow-list (its number of entries; the addresses are not recorded here), the per-transaction and daily amount ceilings and their currency. They hold on every node from the next request. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.api_key.restricted/v1",
  "title": "credential.api_key.restricted",
  "description": "An API key's expiry or restrictions changed (PSP-8.4): the IP allow-list (its number of entries; the addresses are not recorded here), the per-transaction and daily amount ceilings and their currency. They hold on every node from the next request.",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "Audit record."
  },
  "type": "object",
  "required": [
    "api_key_id",
    "allowed_ips"
  ],
  "properties": {
    "api_key_id": {
      "type": "string"
    },
    "allowed_ips": {
      "type": "integer"
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "max_amount": {
      "type": "string"
    },
    "daily_amount": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

credential.api_key.revoked

An API key was revoked. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.api_key.revoked/v1",
  "title": "credential.api_key.revoked",
  "description": "An API key was revoked.",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/api_key.json"
    },
    "after": {
      "$ref": "kulpay://objects/api_key.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

credential.api_key.rotated

An API key was rolled (PSP-8.3): a new key with the same scopes was issued, and the old one keeps working until expires_at, the end of the grace period. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.api_key.rotated/v1",
  "title": "credential.api_key.rotated",
  "description": "An API key was rolled (PSP-8.3): a new key with the same scopes was issued, and the old one keeps working until expires_at, the end of the grace period.",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "Deploy the new key everywhere before expires_at; the old key's use count shows whether anything still uses it."
  },
  "type": "object",
  "required": [
    "api_key_id",
    "new_api_key_id",
    "expires_at",
    "grace_seconds"
  ],
  "properties": {
    "api_key_id": {
      "type": "string"
    },
    "new_api_key_id": {
      "type": "string"
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "grace_seconds": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

credential.blocked_ip

A call with an API key was refused with 403 ip_not_allowed: it came from an address outside the key's allow-list (PSP-8.4). Recorded at most once per key and address every ten minutes. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.blocked_ip/v1",
  "title": "credential.blocked_ip",
  "description": "A call with an API key was refused with 403 ip_not_allowed: it came from an address outside the key's allow-list (PSP-8.4). Recorded at most once per key and address every ten minutes.",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "Check whether the key leaked; revoke it if the address is not the holder's own."
  },
  "type": "object",
  "required": [
    "api_key_id",
    "ip"
  ],
  "properties": {
    "api_key_id": {
      "type": "string"
    },
    "ip": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

credential.usage_spike

An API key made more than 5 times its 7-day average of requests today (with a baseline of at least 10 a day) (PSP-8.5). Recorded once a day. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.usage_spike/v1",
  "title": "credential.usage_spike",
  "description": "An API key made more than 5 times its 7-day average of requests today (with a baseline of at least 10 a day) (PSP-8.5). Recorded once a day.",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "Confirm the traffic is the holder's (a launch, a batch job); otherwise revoke the key."
  },
  "type": "object",
  "required": [
    "api_key_id",
    "day",
    "requests",
    "baseline"
  ],
  "properties": {
    "api_key_id": {
      "type": "string"
    },
    "day": {
      "type": "string"
    },
    "requests": {
      "type": "integer"
    },
    "baseline": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

credential.used_from_new_location

An API key was used from a location it had not been used from before (PSP-8.5): a new country or ASN where the edge reports them, else a new network (IPv4 /16, IPv6 /32). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.used_from_new_location/v1",
  "title": "credential.used_from_new_location",
  "description": "An API key was used from a location it had not been used from before (PSP-8.5): a new country or ASN where the edge reports them, else a new network (IPv4 /16, IPv6 /32).",
  "x-kulpay": {
    "aggregate": "api_key",
    "webhook": false,
    "terminal": false,
    "act": "Confirm with the key's holder; if they did not move, revoke the key."
  },
  "type": "object",
  "required": [
    "api_key_id",
    "location",
    "known_locations"
  ],
  "properties": {
    "api_key_id": {
      "type": "string"
    },
    "location": {
      "type": "string"
    },
    "ip": {
      "type": "string"
    },
    "known_locations": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

client_ip events

EventWebhookTerminalFires whenWhat to do
credential.auth_failures_spike v1 no no More than 50 failed authentications came from one address in 5 minutes (PSP-8.5): a credential-stuffing or key-guessing attempt, or a client with a revoked key in a retry loop. A failed token names no key, so the address is reported. Block the address at the edge if it is not a known client.

credential.auth_failures_spike

More than 50 failed authentications came from one address in 5 minutes (PSP-8.5): a credential-stuffing or key-guessing attempt, or a client with a revoked key in a retry loop. A failed token names no key, so the address is reported. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.auth_failures_spike/v1",
  "title": "credential.auth_failures_spike",
  "description": "More than 50 failed authentications came from one address in 5 minutes (PSP-8.5): a credential-stuffing or key-guessing attempt, or a client with a revoked key in a retry loop. A failed token names no key, so the address is reported.",
  "x-kulpay": {
    "aggregate": "client_ip",
    "webhook": false,
    "terminal": false,
    "act": "Block the address at the edge if it is not a known client."
  },
  "type": "object",
  "required": [
    "ip",
    "failures",
    "window_seconds"
  ],
  "properties": {
    "ip": {
      "type": "string"
    },
    "failures": {
      "type": "integer"
    },
    "window_seconds": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

client_certificate events

EventWebhookTerminalFires whenWhat to do
credential.certificate.expiring v1 no no A client certificate expires soon (PSP-8.7): reminders 30, 7 and 1 days before, also emailed. Send a new CSR and deploy the new certificate before expiry; both work until then.
credential.certificate.issued v1 no no The regional CA signed a client certificate from an account's CSR (PSP-8.7), valid 13 months. Audit record.
credential.certificate.revoked v1 no yes A client certificate was revoked (PSP-8.7); calls presenting it are refused from the next request. Audit record.

credential.certificate.expiring

A client certificate expires soon (PSP-8.7): reminders 30, 7 and 1 days before, also emailed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.certificate.expiring/v1",
  "title": "credential.certificate.expiring",
  "description": "A client certificate expires soon (PSP-8.7): reminders 30, 7 and 1 days before, also emailed.",
  "x-kulpay": {
    "aggregate": "client_certificate",
    "webhook": false,
    "terminal": false,
    "act": "Send a new CSR and deploy the new certificate before expiry; both work until then."
  },
  "type": "object",
  "required": [
    "certificate_id",
    "not_after",
    "days_left"
  ],
  "properties": {
    "certificate_id": {
      "type": "string"
    },
    "not_after": {
      "type": "string",
      "format": "date-time"
    },
    "days_left": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

credential.certificate.issued

The regional CA signed a client certificate from an account's CSR (PSP-8.7), valid 13 months. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.certificate.issued/v1",
  "title": "credential.certificate.issued",
  "description": "The regional CA signed a client certificate from an account's CSR (PSP-8.7), valid 13 months.",
  "x-kulpay": {
    "aggregate": "client_certificate",
    "webhook": false,
    "terminal": false,
    "act": "Audit record."
  },
  "type": "object",
  "required": [
    "certificate_id",
    "serial",
    "fingerprint",
    "not_after"
  ],
  "properties": {
    "certificate_id": {
      "type": "string"
    },
    "serial": {
      "type": "string"
    },
    "fingerprint": {
      "type": "string"
    },
    "not_after": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

credential.certificate.revoked

A client certificate was revoked (PSP-8.7); calls presenting it are refused from the next request. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.certificate.revoked/v1",
  "title": "credential.certificate.revoked",
  "description": "A client certificate was revoked (PSP-8.7); calls presenting it are refused from the next request.",
  "x-kulpay": {
    "aggregate": "client_certificate",
    "webhook": false,
    "terminal": true,
    "act": "Audit record."
  },
  "type": "object",
  "required": [
    "certificate_id"
  ],
  "properties": {
    "certificate_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

client_ca events

EventWebhookTerminalFires whenWhat to do
credential.client_ca.created v1 no no A region's private certificate authority for client certificates was created on first use (PSP-8.7); its key is envelope-encrypted. Distribute its certificate (GET /v1/client-ca) to the edge's client verification.

credential.client_ca.created

A region's private certificate authority for client certificates was created on first use (PSP-8.7); its key is envelope-encrypted. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.client_ca.created/v1",
  "title": "credential.client_ca.created",
  "description": "A region's private certificate authority for client certificates was created on first use (PSP-8.7); its key is envelope-encrypted.",
  "x-kulpay": {
    "aggregate": "client_ca",
    "webhook": false,
    "terminal": false,
    "act": "Distribute its certificate (GET /v1/client-ca) to the edge's client verification."
  },
  "type": "object",
  "required": [
    "region",
    "not_after"
  ],
  "properties": {
    "region": {
      "type": "string"
    },
    "not_after": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

kek events

EventWebhookTerminalFires whenWhat to do
credential.kek.rotated v1 no no Secrets sealed under a retired KEK, or an older version of the active one, were re-wrapped under the active KEK (PSP-8.9). Only the wrapped data keys change; no secret is decrypted to disk or re-encrypted. None while it repeats; once no secret is left under a retired KEK it can be removed from encryption.retired_keys.

credential.kek.rotated

Secrets sealed under a retired KEK, or an older version of the active one, were re-wrapped under the active KEK (PSP-8.9). Only the wrapped data keys change; no secret is decrypted to disk or re-encrypted. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.kek.rotated/v1",
  "title": "credential.kek.rotated",
  "description": "Secrets sealed under a retired KEK, or an older version of the active one, were re-wrapped under the active KEK (PSP-8.9). Only the wrapped data keys change; no secret is decrypted to disk or re-encrypted.",
  "x-kulpay": {
    "aggregate": "kek",
    "webhook": false,
    "terminal": false,
    "act": "None while it repeats; once no secret is left under a retired KEK it can be removed from encryption.retired_keys."
  },
  "type": "object",
  "required": [
    "key_id",
    "rewrapped"
  ],
  "properties": {
    "key_id": {
      "type": "string"
    },
    "rewrapped": {
      "type": "integer"
    },
    "columns": {
      "type": "object",
      "additionalProperties": {
        "type": "integer"
      }
    }
  },
  "additionalProperties": false
}

oauth_client events

EventWebhookTerminalFires whenWhat to do
credential.oauth_client.created v1 no no An OAuth client was registered for the client credentials grant (PSP-8.7), authenticating with a secret or a private-key JWT (RFC 7523). Audit record.
credential.oauth_client.revoked v1 no yes An OAuth client was revoked (PSP-8.7); its access tokens stop working at once. Audit record.

credential.oauth_client.created

An OAuth client was registered for the client credentials grant (PSP-8.7), authenticating with a secret or a private-key JWT (RFC 7523). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.oauth_client.created/v1",
  "title": "credential.oauth_client.created",
  "description": "An OAuth client was registered for the client credentials grant (PSP-8.7), authenticating with a secret or a private-key JWT (RFC 7523).",
  "x-kulpay": {
    "aggregate": "oauth_client",
    "webhook": false,
    "terminal": false,
    "act": "Audit record."
  },
  "type": "object",
  "required": [
    "client_id",
    "name",
    "auth_method",
    "scopes"
  ],
  "properties": {
    "client_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "auth_method": {
      "enum": [
        "client_secret",
        "private_key_jwt"
      ]
    },
    "scopes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

credential.oauth_client.revoked

An OAuth client was revoked (PSP-8.7); its access tokens stop working at once. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.oauth_client.revoked/v1",
  "title": "credential.oauth_client.revoked",
  "description": "An OAuth client was revoked (PSP-8.7); its access tokens stop working at once.",
  "x-kulpay": {
    "aggregate": "oauth_client",
    "webhook": false,
    "terminal": true,
    "act": "Audit record."
  },
  "type": "object",
  "required": [
    "client_id"
  ],
  "properties": {
    "client_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp_secret events

EventWebhookTerminalFires whenWhat to do
credential.psp_secret.created v1 no no A PSP credential was stored, envelope-encrypted under the active KEK (PSP-8.9): an API key, password, certificate, PGP or SSH key, or a connector binding's configuration. Only its metadata is recorded. Audit record. Check the owner and the rotation date.
credential.psp_secret.rotated v1 no no A PSP credential was replaced by a new value (PSP-8.9); its version moved on and its rotation reminder restarted. Audit record. Confirm the PSP accepts the new credential.
credential.psp_secret.rotation_due v1 no no A PSP credential is due for rotation, or expires within 30 days (PSP-8.9). Repeated weekly until it is rotated. Its owner obtains a new credential from the PSP and rotates it on /admin/credentials before it expires.

credential.psp_secret.created

A PSP credential was stored, envelope-encrypted under the active KEK (PSP-8.9): an API key, password, certificate, PGP or SSH key, or a connector binding's configuration. Only its metadata is recorded. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.psp_secret.created/v1",
  "title": "credential.psp_secret.created",
  "description": "A PSP credential was stored, envelope-encrypted under the active KEK (PSP-8.9): an API key, password, certificate, PGP or SSH key, or a connector binding's configuration. Only its metadata is recorded.",
  "x-kulpay": {
    "aggregate": "psp_secret",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Check the owner and the rotation date."
  },
  "type": "object",
  "required": [
    "secret"
  ],
  "properties": {
    "secret": {
      "$ref": "kulpay://objects/psp_secret.json"
    }
  },
  "additionalProperties": false
}

credential.psp_secret.rotated

A PSP credential was replaced by a new value (PSP-8.9); its version moved on and its rotation reminder restarted. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.psp_secret.rotated/v1",
  "title": "credential.psp_secret.rotated",
  "description": "A PSP credential was replaced by a new value (PSP-8.9); its version moved on and its rotation reminder restarted.",
  "x-kulpay": {
    "aggregate": "psp_secret",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Confirm the PSP accepts the new credential."
  },
  "type": "object",
  "required": [
    "secret"
  ],
  "properties": {
    "secret": {
      "$ref": "kulpay://objects/psp_secret.json"
    },
    "previous_version": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

credential.psp_secret.rotation_due

A PSP credential is due for rotation, or expires within 30 days (PSP-8.9). Repeated weekly until it is rotated. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.psp_secret.rotation_due/v1",
  "title": "credential.psp_secret.rotation_due",
  "description": "A PSP credential is due for rotation, or expires within 30 days (PSP-8.9). Repeated weekly until it is rotated.",
  "x-kulpay": {
    "aggregate": "psp_secret",
    "webhook": false,
    "terminal": false,
    "act": "Its owner obtains a new credential from the PSP and rotates it on /admin/credentials before it expires."
  },
  "type": "object",
  "required": [
    "secret",
    "reason"
  ],
  "properties": {
    "secret": {
      "$ref": "kulpay://objects/psp_secret.json"
    },
    "reason": {
      "enum": [
        "rotation_due",
        "expiring",
        "expired"
      ]
    }
  },
  "additionalProperties": false
}

region_key events

EventWebhookTerminalFires whenWhat to do
credential.region_key.created v1 no no KulPay created a per-region key for statements (PSP-11.2): the SFTP host key or the PGP key PSPs encrypt statements to. The private half is sealed by the KEK and re-wrapped with it. Publish the fingerprint to PSPs (GET /v1/psps/{id}/statement-endpoints).

credential.region_key.created

KulPay created a per-region key for statements (PSP-11.2): the SFTP host key or the PGP key PSPs encrypt statements to. The private half is sealed by the KEK and re-wrapped with it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.region_key.created/v1",
  "title": "credential.region_key.created",
  "description": "KulPay created a per-region key for statements (PSP-11.2): the SFTP host key or the PGP key PSPs encrypt statements to. The private half is sealed by the KEK and re-wrapped with it.",
  "x-kulpay": {
    "aggregate": "region_key",
    "webhook": false,
    "terminal": false,
    "act": "Publish the fingerprint to PSPs (GET /v1/psps/{id}/statement-endpoints)."
  },
  "type": "object",
  "required": [
    "region",
    "kind",
    "fingerprint"
  ],
  "properties": {
    "region": {
      "type": "string"
    },
    "kind": {
      "type": "string",
      "enum": [
        "sftp_host",
        "kulpay_pgp"
      ]
    },
    "fingerprint": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

webhook_signing_key events

EventWebhookTerminalFires whenWhat to do
credential.webhook_signing_key.created v1 no no A region's Ed25519 webhook signing key was created on first use (PSP-8.8); its public half is published at /.well-known/kulpay-webhook-keys. None.

credential.webhook_signing_key.created

A region's Ed25519 webhook signing key was created on first use (PSP-8.8); its public half is published at /.well-known/kulpay-webhook-keys. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.webhook_signing_key.created/v1",
  "title": "credential.webhook_signing_key.created",
  "description": "A region's Ed25519 webhook signing key was created on first use (PSP-8.8); its public half is published at /.well-known/kulpay-webhook-keys.",
  "x-kulpay": {
    "aggregate": "webhook_signing_key",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "region",
    "kid"
  ],
  "properties": {
    "region": {
      "type": "string"
    },
    "kid": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

data_transfer events

EventWebhookTerminalFires whenWhat to do
data_transfer.blocked_field v1 no no A field of an outbound request to a connector outside the region was dropped because the connector's allow-list does not permit it (PSP-12.3), such as a national ID, a phone number or KYB data. The value is never recorded, only the field name. None for one drop. A field dropped on every call may mean an integration relies on it: add it to the connector's allow-list only if the transfer register's legal basis covers it.
data_transfer.sent v1 no no A payment or payout was sent to a connector outside the region (PSP-12.3), carrying only the fields the connector's allow-list permits; the fields sent are listed. Recorded before the call: a transfer that cannot be recorded is not sent. None. Evidence for the transfer register and the residency rule.

data_transfer.blocked_field

A field of an outbound request to a connector outside the region was dropped because the connector's allow-list does not permit it (PSP-12.3), such as a national ID, a phone number or KYB data. The value is never recorded, only the field name. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/data_transfer.blocked_field/v1",
  "title": "data_transfer.blocked_field",
  "description": "A field of an outbound request to a connector outside the region was dropped because the connector's allow-list does not permit it (PSP-12.3), such as a national ID, a phone number or KYB data. The value is never recorded, only the field name.",
  "x-kulpay": {
    "aggregate": "data_transfer",
    "webhook": false,
    "terminal": false,
    "act": "None for one drop. A field dropped on every call may mean an integration relies on it: add it to the connector's allow-list only if the transfer register's legal basis covers it."
  },
  "type": "object",
  "required": [
    "transaction_type",
    "transaction_id",
    "connector",
    "country",
    "field"
  ],
  "properties": {
    "transaction_type": {
      "enum": [
        "payment",
        "payout"
      ]
    },
    "transaction_id": {
      "type": "string"
    },
    "connector": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "field": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

data_transfer.sent

A payment or payout was sent to a connector outside the region (PSP-12.3), carrying only the fields the connector's allow-list permits; the fields sent are listed. Recorded before the call: a transfer that cannot be recorded is not sent. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/data_transfer.sent/v1",
  "title": "data_transfer.sent",
  "description": "A payment or payout was sent to a connector outside the region (PSP-12.3), carrying only the fields the connector's allow-list permits; the fields sent are listed. Recorded before the call: a transfer that cannot be recorded is not sent.",
  "x-kulpay": {
    "aggregate": "data_transfer",
    "webhook": false,
    "terminal": false,
    "act": "None. Evidence for the transfer register and the residency rule."
  },
  "type": "object",
  "required": [
    "transaction_type",
    "transaction_id",
    "connector",
    "country",
    "legal_entity",
    "fields"
  ],
  "properties": {
    "transaction_type": {
      "enum": [
        "payment",
        "payout"
      ]
    },
    "transaction_id": {
      "type": "string"
    },
    "connector": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "legal_entity": {
      "type": "string"
    },
    "fields": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

webhook_delivery events

EventWebhookTerminalFires whenWhat to do
delivery.attempted v1 no no One attempt to deliver a webhook (PSP-9.5): its answer, latency and what happens next. Attempts run immediately, then at 1 min, 5 min, 15 min, 1 h, 3 h, 8 h and 24 h, each with a 10 s timeout. None.
delivery.exhausted v1 no yes A webhook failed its last attempt, after 24 hours, or the endpoint answered 410 Gone, and went to the dead-letter queue (PSP-9.5). The endpoint's contacts are alerted. The receiver fixes its endpoint, then replays the dead-lettered deliveries (POST /v1/webhook-endpoints/{id}/replay?status=exhausted).
delivery.succeeded v1 no yes A webhook was delivered: the endpoint answered 2xx (PSP-9.5). None.

delivery.attempted

One attempt to deliver a webhook (PSP-9.5): its answer, latency and what happens next. Attempts run immediately, then at 1 min, 5 min, 15 min, 1 h, 3 h, 8 h and 24 h, each with a 10 s timeout. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/delivery.attempted/v1",
  "title": "delivery.attempted",
  "description": "One attempt to deliver a webhook (PSP-9.5): its answer, latency and what happens next. Attempts run immediately, then at 1 min, 5 min, 15 min, 1 h, 3 h, 8 h and 24 h, each with a 10 s timeout.",
  "x-kulpay": {
    "aggregate": "webhook_delivery",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "delivery_id",
    "endpoint_id",
    "event_type",
    "attempt",
    "outcome"
  ],
  "properties": {
    "delivery_id": {
      "type": "string"
    },
    "endpoint_id": {
      "type": "string"
    },
    "event_id": {
      "type": "string"
    },
    "event_type": {
      "type": "string"
    },
    "attempt": {
      "type": "integer"
    },
    "response_code": {
      "type": "integer"
    },
    "duration_ms": {
      "type": "integer"
    },
    "outcome": {
      "enum": [
        "delivered",
        "retrying",
        "exhausted",
        "gone"
      ]
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

delivery.exhausted

A webhook failed its last attempt, after 24 hours, or the endpoint answered 410 Gone, and went to the dead-letter queue (PSP-9.5). The endpoint's contacts are alerted. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/delivery.exhausted/v1",
  "title": "delivery.exhausted",
  "description": "A webhook failed its last attempt, after 24 hours, or the endpoint answered 410 Gone, and went to the dead-letter queue (PSP-9.5). The endpoint's contacts are alerted.",
  "x-kulpay": {
    "aggregate": "webhook_delivery",
    "webhook": false,
    "terminal": true,
    "act": "The receiver fixes its endpoint, then replays the dead-lettered deliveries (POST /v1/webhook-endpoints/{id}/replay?status=exhausted)."
  },
  "type": "object",
  "required": [
    "delivery_id",
    "endpoint_id",
    "event_type",
    "attempts"
  ],
  "properties": {
    "delivery_id": {
      "type": "string"
    },
    "endpoint_id": {
      "type": "string"
    },
    "event_id": {
      "type": "string"
    },
    "event_type": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

delivery.succeeded

A webhook was delivered: the endpoint answered 2xx (PSP-9.5). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/delivery.succeeded/v1",
  "title": "delivery.succeeded",
  "description": "A webhook was delivered: the endpoint answered 2xx (PSP-9.5).",
  "x-kulpay": {
    "aggregate": "webhook_delivery",
    "webhook": false,
    "terminal": true,
    "act": "None."
  },
  "type": "object",
  "required": [
    "delivery_id",
    "endpoint_id",
    "event_type",
    "attempts"
  ],
  "properties": {
    "delivery_id": {
      "type": "string"
    },
    "endpoint_id": {
      "type": "string"
    },
    "event_id": {
      "type": "string"
    },
    "event_type": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

dispute events

EventWebhookTerminalFires whenWhat to do
dispute.created v1 yes no A payment was disputed (F2-5): by the acquirer (a chargeback, source provider) or by an operator on a rail without acquirer disputes (a complaint, source operator). The amount left the available balance for disputes_hold until the dispute is decided. Answer with evidence before the deadline, or the dispute is lost. Gather evidence and POST /v1/disputes/{id}/evidence before the deadline, or POST /v1/disputes/{id}/accept.
dispute.deadline_approaching v1 yes no A dispute still waits for the merchant's evidence and its deadline is near (F2-5): days_left is 5, then 1. Unanswered by the deadline, it is lost. Submit the evidence (POST /v1/disputes/{id}/evidence) or accept the dispute.
dispute.evidence_submitted v1 yes no The evidence of a dispute was submitted for review (F2-5): via merchant, through the API or the portal; via acquirer, on the acquirer's own dashboard. The dispute is under_review until the acquirer decides. Wait for dispute.won or dispute.lost.
dispute.lost v1 yes yes A dispute was lost (F2-5): by the acquirer's decision, conceded by the merchant, or left unanswered past its deadline (close_reason says which). The held amount is gone for good, and fee is the chargeback fee of the commission schedule (0 without a DISPUTE rule). Reconcile the loss; fee was taken from the balance.
dispute.won v1 yes yes A dispute was decided in the merchant's favour (F2-5): the held amount is back in the available balance and the payment stands. Nothing: the money is available again.

dispute.created

A payment was disputed (F2-5): by the acquirer (a chargeback, source provider) or by an operator on a rail without acquirer disputes (a complaint, source operator). The amount left the available balance for disputes_hold until the dispute is decided. Answer with evidence before the deadline, or the dispute is lost. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/dispute.created/v1",
  "title": "dispute.created",
  "description": "A payment was disputed (F2-5): by the acquirer (a chargeback, source provider) or by an operator on a rail without acquirer disputes (a complaint, source operator). The amount left the available balance for disputes_hold until the dispute is decided. Answer with evidence before the deadline, or the dispute is lost.",
  "x-kulpay": {
    "aggregate": "dispute",
    "webhook": true,
    "terminal": false,
    "act": "Gather evidence and POST /v1/disputes/{id}/evidence before the deadline, or POST /v1/disputes/{id}/accept."
  },
  "type": "object",
  "required": [
    "dispute_id",
    "payment_id",
    "account_id",
    "amount",
    "currency",
    "reason",
    "status",
    "source"
  ],
  "properties": {
    "dispute_id": {
      "type": "string"
    },
    "payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "amount": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "fraudulent",
        "unrecognized",
        "duplicate",
        "product_not_received",
        "product_unacceptable",
        "subscription_canceled",
        "credit_not_processed",
        "general"
      ]
    },
    "status": {
      "enum": [
        "needs_response",
        "under_review",
        "won",
        "lost"
      ]
    },
    "source": {
      "enum": [
        "provider",
        "operator"
      ]
    },
    "deadline": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

dispute.deadline_approaching

A dispute still waits for the merchant's evidence and its deadline is near (F2-5): days_left is 5, then 1. Unanswered by the deadline, it is lost. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/dispute.deadline_approaching/v1",
  "title": "dispute.deadline_approaching",
  "description": "A dispute still waits for the merchant's evidence and its deadline is near (F2-5): days_left is 5, then 1. Unanswered by the deadline, it is lost.",
  "x-kulpay": {
    "aggregate": "dispute",
    "webhook": true,
    "terminal": false,
    "act": "Submit the evidence (POST /v1/disputes/{id}/evidence) or accept the dispute."
  },
  "type": "object",
  "required": [
    "dispute_id",
    "payment_id",
    "account_id",
    "amount",
    "currency",
    "reason",
    "status",
    "source",
    "days_left",
    "deadline"
  ],
  "properties": {
    "dispute_id": {
      "type": "string"
    },
    "payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "amount": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "fraudulent",
        "unrecognized",
        "duplicate",
        "product_not_received",
        "product_unacceptable",
        "subscription_canceled",
        "credit_not_processed",
        "general"
      ]
    },
    "status": {
      "enum": [
        "needs_response",
        "under_review",
        "won",
        "lost"
      ]
    },
    "source": {
      "enum": [
        "provider",
        "operator"
      ]
    },
    "deadline": {
      "type": "string",
      "format": "date-time"
    },
    "days_left": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

dispute.evidence_submitted

The evidence of a dispute was submitted for review (F2-5): via merchant, through the API or the portal; via acquirer, on the acquirer's own dashboard. The dispute is under_review until the acquirer decides. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/dispute.evidence_submitted/v1",
  "title": "dispute.evidence_submitted",
  "description": "The evidence of a dispute was submitted for review (F2-5): via merchant, through the API or the portal; via acquirer, on the acquirer's own dashboard. The dispute is under_review until the acquirer decides.",
  "x-kulpay": {
    "aggregate": "dispute",
    "webhook": true,
    "terminal": false,
    "act": "Wait for dispute.won or dispute.lost."
  },
  "type": "object",
  "required": [
    "dispute_id",
    "payment_id",
    "account_id",
    "amount",
    "currency",
    "reason",
    "status",
    "source",
    "via"
  ],
  "properties": {
    "dispute_id": {
      "type": "string"
    },
    "payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "amount": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "fraudulent",
        "unrecognized",
        "duplicate",
        "product_not_received",
        "product_unacceptable",
        "subscription_canceled",
        "credit_not_processed",
        "general"
      ]
    },
    "status": {
      "enum": [
        "needs_response",
        "under_review",
        "won",
        "lost"
      ]
    },
    "source": {
      "enum": [
        "provider",
        "operator"
      ]
    },
    "deadline": {
      "type": "string",
      "format": "date-time"
    },
    "via": {
      "enum": [
        "merchant",
        "acquirer"
      ]
    },
    "fields": {
      "type": "integer"
    },
    "documents": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

dispute.lost

A dispute was lost (F2-5): by the acquirer's decision, conceded by the merchant, or left unanswered past its deadline (close_reason says which). The held amount is gone for good, and fee is the chargeback fee of the commission schedule (0 without a DISPUTE rule). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/dispute.lost/v1",
  "title": "dispute.lost",
  "description": "A dispute was lost (F2-5): by the acquirer's decision, conceded by the merchant, or left unanswered past its deadline (close_reason says which). The held amount is gone for good, and fee is the chargeback fee of the commission schedule (0 without a DISPUTE rule).",
  "x-kulpay": {
    "aggregate": "dispute",
    "webhook": true,
    "terminal": true,
    "act": "Reconcile the loss; fee was taken from the balance."
  },
  "type": "object",
  "required": [
    "dispute_id",
    "payment_id",
    "account_id",
    "amount",
    "currency",
    "reason",
    "status",
    "source",
    "fee"
  ],
  "properties": {
    "dispute_id": {
      "type": "string"
    },
    "payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "amount": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "fraudulent",
        "unrecognized",
        "duplicate",
        "product_not_received",
        "product_unacceptable",
        "subscription_canceled",
        "credit_not_processed",
        "general"
      ]
    },
    "status": {
      "enum": [
        "needs_response",
        "under_review",
        "won",
        "lost"
      ]
    },
    "source": {
      "enum": [
        "provider",
        "operator"
      ]
    },
    "deadline": {
      "type": "string",
      "format": "date-time"
    },
    "close_reason": {
      "type": "string"
    },
    "fee": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

dispute.won

A dispute was decided in the merchant's favour (F2-5): the held amount is back in the available balance and the payment stands. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/dispute.won/v1",
  "title": "dispute.won",
  "description": "A dispute was decided in the merchant's favour (F2-5): the held amount is back in the available balance and the payment stands.",
  "x-kulpay": {
    "aggregate": "dispute",
    "webhook": true,
    "terminal": true,
    "act": "Nothing: the money is available again."
  },
  "type": "object",
  "required": [
    "dispute_id",
    "payment_id",
    "account_id",
    "amount",
    "currency",
    "reason",
    "status",
    "source"
  ],
  "properties": {
    "dispute_id": {
      "type": "string"
    },
    "payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "amount": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "fraudulent",
        "unrecognized",
        "duplicate",
        "product_not_received",
        "product_unacceptable",
        "subscription_canceled",
        "credit_not_processed",
        "general"
      ]
    },
    "status": {
      "enum": [
        "needs_response",
        "under_review",
        "won",
        "lost"
      ]
    },
    "source": {
      "enum": [
        "provider",
        "operator"
      ]
    },
    "deadline": {
      "type": "string",
      "format": "date-time"
    },
    "close_reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

file events

EventWebhookTerminalFires whenWhat to do
file.attached v1 no no A file was attached to a payment. Audit record. Compare before and after.
file.deleted v1 no no A file was deleted. Audit record. Compare before and after.
file.uploaded v1 no no A file was uploaded. Audit record. Compare before and after.

file.attached

A file was attached to a payment. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/file.attached/v1",
  "title": "file.attached",
  "description": "A file was attached to a payment.",
  "x-kulpay": {
    "aggregate": "file",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/file.json"
    },
    "after": {
      "$ref": "kulpay://objects/file.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

file.deleted

A file was deleted. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/file.deleted/v1",
  "title": "file.deleted",
  "description": "A file was deleted.",
  "x-kulpay": {
    "aggregate": "file",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/file.json"
    },
    "after": {
      "$ref": "kulpay://objects/file.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

file.uploaded

A file was uploaded. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/file.uploaded/v1",
  "title": "file.uploaded",
  "description": "A file was uploaded.",
  "x-kulpay": {
    "aggregate": "file",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/file.json"
    },
    "after": {
      "$ref": "kulpay://objects/file.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

job events

EventWebhookTerminalFires whenWhat to do
job.run.completed v1 no no A singleton job run finished. Operators only. Each singleton job run is recorded with the node that led it.
job.run.failed v1 no no A singleton job run failed. Operators only. Each singleton job run is recorded with the node that led it.
job.run.started v1 no no A singleton job run began on the leader node. Operators only. Each singleton job run is recorded with the node that led it.

job.run.completed

A singleton job run finished. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/job.run.completed/v1",
  "title": "job.run.completed",
  "description": "A singleton job run finished.",
  "x-kulpay": {
    "aggregate": "job",
    "webhook": false,
    "terminal": false,
    "act": "Operators only. Each singleton job run is recorded with the node that led it."
  },
  "type": "object",
  "required": [
    "job",
    "run_id",
    "region",
    "node",
    "duration_ms"
  ],
  "properties": {
    "job": {
      "type": "string",
      "description": "Job name, e.g. settlement_sync."
    },
    "run_id": {
      "type": "string"
    },
    "region": {
      "type": "string"
    },
    "node": {
      "type": "string",
      "description": "The leader that ran it."
    },
    "duration_ms": {
      "type": "integer",
      "minimum": 0
    },
    "counts": {
      "type": "object",
      "additionalProperties": {
        "type": "integer"
      },
      "description": "What the run did, e.g. {\"swept\": 12}."
    }
  },
  "additionalProperties": false
}

job.run.failed

A singleton job run failed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/job.run.failed/v1",
  "title": "job.run.failed",
  "description": "A singleton job run failed.",
  "x-kulpay": {
    "aggregate": "job",
    "webhook": false,
    "terminal": false,
    "act": "Operators only. Each singleton job run is recorded with the node that led it."
  },
  "type": "object",
  "required": [
    "job",
    "run_id",
    "region",
    "node",
    "duration_ms",
    "error"
  ],
  "properties": {
    "job": {
      "type": "string",
      "description": "Job name, e.g. settlement_sync."
    },
    "run_id": {
      "type": "string"
    },
    "region": {
      "type": "string"
    },
    "node": {
      "type": "string",
      "description": "The leader that ran it."
    },
    "duration_ms": {
      "type": "integer",
      "minimum": 0
    },
    "error": {
      "type": "string",
      "maxLength": 2000
    },
    "counts": {
      "type": "object",
      "additionalProperties": {
        "type": "integer"
      }
    }
  },
  "additionalProperties": false
}

job.run.started

A singleton job run began on the leader node. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/job.run.started/v1",
  "title": "job.run.started",
  "description": "A singleton job run began on the leader node.",
  "x-kulpay": {
    "aggregate": "job",
    "webhook": false,
    "terminal": false,
    "act": "Operators only. Each singleton job run is recorded with the node that led it."
  },
  "type": "object",
  "required": [
    "job",
    "run_id",
    "region",
    "node"
  ],
  "properties": {
    "job": {
      "type": "string",
      "description": "Job name, e.g. settlement_sync."
    },
    "run_id": {
      "type": "string"
    },
    "region": {
      "type": "string"
    },
    "node": {
      "type": "string",
      "description": "The leader that ran it."
    }
  },
  "additionalProperties": false
}

ledger_account events

EventWebhookTerminalFires whenWhat to do
ledger.balance.mismatch v1 no no The nightly ledger verification found a ledger account whose stored running totals differ from the sum of its journal lines (PSP-10.1). Critical: a balance shown to someone may be wrong. Critical: follow runbook 9 (Ledger balance mismatch). Do not pay out from the account until resolved.

ledger.balance.mismatch

The nightly ledger verification found a ledger account whose stored running totals differ from the sum of its journal lines (PSP-10.1). Critical: a balance shown to someone may be wrong. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/ledger.balance.mismatch/v1",
  "title": "ledger.balance.mismatch",
  "description": "The nightly ledger verification found a ledger account whose stored running totals differ from the sum of its journal lines (PSP-10.1). Critical: a balance shown to someone may be wrong.",
  "x-kulpay": {
    "aggregate": "ledger_account",
    "webhook": false,
    "terminal": false,
    "act": "Critical: follow runbook 9 (Ledger balance mismatch). Do not pay out from the account until resolved."
  },
  "type": "object",
  "required": [
    "ledger_account_id",
    "account",
    "currency",
    "severity",
    "stored_debits_minor",
    "stored_credits_minor",
    "derived_debits_minor",
    "derived_credits_minor",
    "difference_minor"
  ],
  "properties": {
    "ledger_account_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "severity": {
      "type": "string",
      "enum": [
        "critical"
      ]
    },
    "stored_debits_minor": {
      "type": "integer"
    },
    "stored_credits_minor": {
      "type": "integer"
    },
    "derived_debits_minor": {
      "type": "integer"
    },
    "derived_credits_minor": {
      "type": "integer"
    },
    "difference_minor": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

journal events

EventWebhookTerminalFires whenWhat to do
ledger.journal.posted v1 no yes A balanced journal was posted to the double-entry ledger (PSP-10.1), in the same database transaction as the money movement it records. Its lines debit and credit named accounts of the tenant's chart; debits equal credits. A reversing journal names the journal it corrects in reverses_id. None: a record of the movement. Balances are derived from journals.

ledger.journal.posted

A balanced journal was posted to the double-entry ledger (PSP-10.1), in the same database transaction as the money movement it records. Its lines debit and credit named accounts of the tenant's chart; debits equal credits. A reversing journal names the journal it corrects in reverses_id. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/ledger.journal.posted/v1",
  "title": "ledger.journal.posted",
  "description": "A balanced journal was posted to the double-entry ledger (PSP-10.1), in the same database transaction as the money movement it records. Its lines debit and credit named accounts of the tenant's chart; debits equal credits. A reversing journal names the journal it corrects in reverses_id.",
  "x-kulpay": {
    "aggregate": "journal",
    "webhook": false,
    "terminal": true,
    "act": "None: a record of the movement. Balances are derived from journals."
  },
  "type": "object",
  "required": [
    "journal_id",
    "kind",
    "currency",
    "total_minor",
    "lines"
  ],
  "properties": {
    "journal_id": {
      "type": "string"
    },
    "kind": {
      "type": "string"
    },
    "transaction_id": {
      "type": "string"
    },
    "reverses_id": {
      "type": "string"
    },
    "memo": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "total_minor": {
      "type": "integer"
    },
    "lines": {
      "type": "array",
      "minItems": 2,
      "items": {
        "type": "object",
        "required": [
          "account",
          "debit_minor",
          "credit_minor"
        ],
        "properties": {
          "account": {
            "type": "string"
          },
          "debit_minor": {
            "type": "integer",
            "minimum": 0
          },
          "credit_minor": {
            "type": "integer",
            "minimum": 0
          }
        },
        "additionalProperties": false
      }
    }
  },
  "additionalProperties": false
}

name_lookup events

EventWebhookTerminalFires whenWhat to do
name_lookup.completed v4 no no A beneficiary name lookup was answered by the PSP. The name itself is never stored: the event keeps a masked form, and the full name lives only in a 10-minute cache. v2 (IO-1.1): routing_rule names the routing rule that chose the resolver ("method_binding" when none matched and the default resolver answered), with the rules version; status may be ambiguous when the resolver offers options. v3 (IO-1.4 to IO-1.6, IO-4.1): reason credit_account_invalid when the resolver named an account that is not the biller's; billers when KulPay resolved the reference itself among a group (routing_rule "biller" when the requester named the biller); option_ids of an ambiguous reference; skipped, the billers of the group that failed or did not answer in time. v4 (QA T-402): status unavailable, reason account_unavailable, for an account that exists but cannot receive (suspended or closed). None.
name_lookup.option_selected v1 no no The requester chose one of the options of an ambiguous reference (IO-1.6): the lookup is now found, with that option's destination and credit account, and a deposit can start from it. The actor is who chose; previous_option_id when the choice changed before any deposit started. None: the requester initiates the deposit.

name_lookup.completed

A beneficiary name lookup was answered by the PSP. The name itself is never stored: the event keeps a masked form, and the full name lives only in a 10-minute cache. v2 (IO-1.1): routing_rule names the routing rule that chose the resolver ("method_binding" when none matched and the default resolver answered), with the rules version; status may be ambiguous when the resolver offers options. v3 (IO-1.4 to IO-1.6, IO-4.1): reason credit_account_invalid when the resolver named an account that is not the biller's; billers when KulPay resolved the reference itself among a group (routing_rule "biller" when the requester named the biller); option_ids of an ambiguous reference; skipped, the billers of the group that failed or did not answer in time. v4 (QA T-402): status unavailable, reason account_unavailable, for an account that exists but cannot receive (suspended or closed). Schema (JSON)

Schema v4
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/name_lookup.completed/v4",
  "title": "name_lookup.completed",
  "description": "A beneficiary name lookup was answered by the PSP. The name itself is never stored: the event keeps a masked form, and the full name lives only in a 10-minute cache. v2 (IO-1.1): routing_rule names the routing rule that chose the resolver (\"method_binding\" when none matched and the default resolver answered), with the rules version; status may be ambiguous when the resolver offers options. v3 (IO-1.4 to IO-1.6, IO-4.1): reason credit_account_invalid when the resolver named an account that is not the biller's; billers when KulPay resolved the reference itself among a group (routing_rule \"biller\" when the requester named the biller); option_ids of an ambiguous reference; skipped, the billers of the group that failed or did not answer in time. v4 (QA T-402): status unavailable, reason account_unavailable, for an account that exists but cannot receive (suspended or closed).",
  "x-kulpay": {
    "aggregate": "name_lookup",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "lookup_id",
    "method",
    "status"
  ],
  "properties": {
    "lookup_id": {
      "type": "string"
    },
    "method": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "found",
        "not_found",
        "ambiguous",
        "unavailable"
      ]
    },
    "masked_name": {
      "type": "string"
    },
    "masked_value": {
      "type": "string"
    },
    "raw_code": {
      "type": "string"
    },
    "routing_rule": {
      "type": "string"
    },
    "rules_version": {
      "type": "integer",
      "minimum": 0
    },
    "reason": {
      "enum": [
        "credit_account_invalid",
        "account_unavailable"
      ]
    },
    "billers": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The billers KulPay asked itself (IO-1.5)."
    },
    "option_ids": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The options of an ambiguous reference (IO-1.6)."
    },
    "skipped": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "biller",
          "error"
        ],
        "properties": {
          "biller": {
            "type": "string"
          },
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "description": "Billers of the group left out: they failed or did not answer within 3 s."
    },
    "latency_ms": {
      "type": "integer",
      "description": "How long the resolver took to answer, in milliseconds (IO-7.2)."
    }
  },
  "additionalProperties": false
}

name_lookup.option_selected

The requester chose one of the options of an ambiguous reference (IO-1.6): the lookup is now found, with that option's destination and credit account, and a deposit can start from it. The actor is who chose; previous_option_id when the choice changed before any deposit started. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/name_lookup.option_selected/v1",
  "title": "name_lookup.option_selected",
  "description": "The requester chose one of the options of an ambiguous reference (IO-1.6): the lookup is now found, with that option's destination and credit account, and a deposit can start from it. The actor is who chose; previous_option_id when the choice changed before any deposit started.",
  "x-kulpay": {
    "aggregate": "name_lookup",
    "webhook": false,
    "terminal": false,
    "act": "None: the requester initiates the deposit."
  },
  "type": "object",
  "required": [
    "lookup_id",
    "option_id"
  ],
  "properties": {
    "lookup_id": {
      "type": "string"
    },
    "option_id": {
      "type": "string"
    },
    "destination_kind": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "product": {
      "type": "string"
    },
    "previous_option_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

payment events

EventWebhookTerminalFires whenWhat to do
payment.authorized v1 yes no The funds are held (authorised) and the payment waits for a capture or a void. An authorisation expires if it is not captured in time. Capture it (POST /v1/payments/{id}/capture) or void it.
payment.cancelled v2 yes yes The payment was cancelled before settling, by the customer or an operator. Release any reservation you made. No money moved.
payment.capture_requested v2 no no A capture was claimed and sent to the provider. Progress only.
payment.created v2 no no A payment was created and is waiting for a method or for the provider. Nothing to do yet; wait for a later event.
payment.disputed v2 yes no The payer disputed the payment (a chargeback or provider dispute). Hold fulfilment where you can and prepare evidence.
payment.evidence_recorded v1 no no Reconciliation evidence (depositor, bank, reference) was recorded on a manual payment. Values are masked. Progress only.
payment.expired v2 yes yes The customer never completed the payment inside its window. Same as cancelled: release, no money moved.
payment.failed v2 yes yes The payment was declined or could not be completed. Do not fulfil. failure_code says why; let the customer retry.
payment.method_selected v2 no no The customer chose a payment method on the hosted checkout. Progress only.
payment.partially_refunded v2 no no Part of the payment was refunded. Informational; act on refund.succeeded.
payment.pending v2 no no The payment is waiting on the provider or the customer. Progress only. Wait for a terminal event.
payment.processing v2 yes no The provider accepted the request and is working on it. Progress only. Show the customer that it is in flight; wait for a terminal event.
payment.provider_call_failed v1 no no A claimed provider call (capture or void) failed; the claim is released so the operation can be retried. The payment's status is unchanged. None; retry the operation if it is still wanted.
payment.refunded v2 no no The payment was fully refunded. Informational; act on refund.succeeded.
payment.requires_action v2 yes no The customer must do something off-page: approve a USSD prompt, enter an OTP, complete 3DS. Progress only. The hosted checkout drives this; if you built your own UI, read next_action.
payment.settlement_changed v1 no no The settlement state of a succeeded payment changed: unsettled, settled by the provider, reconciled against its statement. None.
payment.succeeded v2 yes yes The payment settled and the money is captured. Fulfil the order. This is the only event that means you have been paid.
payment.updated v2 no no Payment details changed without a status change: a provider reference, a next action or a checkout link was recorded. Progress only.
payment.void_requested v2 no no A void was claimed and sent to the provider. Progress only.
psp.code.unmapped v1 no no A provider answered with a code that has no mapping. It is treated as unknown, never as a success or a failure: the payment (or refund) stays unsettled until a status query or reconciliation decides it. Add the code to the provider's mapping on /admin/code-mappings.
reconciliation.payment_exception.raised v1 no no A payment's recorded state and a later report contradict each other about money (a success after a failure, a failure after a success). An operator decides what really happened. Check with the provider and resolve it on /admin/reconciliation.
reconciliation.payment_exception.resolved v1 no no An operator resolved a payment exception. None.
retry.attempted v1 no no One attempt of an outbound operation under a retry policy (PSP-9.2): its number, latency, result and what the engine decided next (retry, failover, status_query, dlq, fail, resolved). None.
retry.exhausted v1 no yes An outbound operation used up its retry policy (PSP-9.2) and took the policy's final action: fail, a reversal or an exception after status queries, or nothing left to fail over to. For status queries: the reversal or the missing_at_psp exception that follows. For submissions: the payment ends with its failure.
retry.scheduled v1 no no An outbound operation's next attempt was scheduled by the PSP's retry policy (PSP-9.2), and stored in the database: a restart loses none. None.
transaction.card.tokenized v1 no no A card payment carries a card the acquirer tokenised (PSP-12.1): KulPay received a token or payment method ID with the display fields only (brand, last 4 digits, expiry), never the card number or CVV. Recorded with the payment it pays for. None. Evidence for PCI scope: the card was collected in the acquirer's hosted fields or tokenised by the merchant's integration.
transaction.failover.attempted v1 no no A payment moved to the next PSP of its routing after a clean failure: a soft decline, or an error before the request was sent (connection refused, DNS failure, an open circuit). Never after the PSP may have accepted it. None. Frequent failovers away from one PSP point at its health or its decline rate.
transaction.outcome.unknown v2 no no A payment's PSP result is unknown: the answer never came back after sending (timeout, dropped connection), it stayed in flight past its window, or the PSP declares no callback for the transaction type (its orientation profile), so status queries start at once. It stays processing while status queries find out what happened. None: status queries run on a schedule; a reversal or an exception follows if the window closes.
transaction.rejected v1 no no A request was refused before any provider call (PSP-5.3): no eligible PSP's capability matrix allows it, its limits have no room, or no PSP is available. No payment was created unless the request belonged to a checkout. None for the platform; the caller got 422 (not_supported, limit_exceeded) or 503 (no_route). Frequent refusals for one rule may mean a capability matrix or routing rule needs a change.
transaction.reversal.requested v1 no no The status window of a payment of unknown outcome closed without an answer; KulPay asked the PSP to reverse it. None if it is reversed (the payment ends cancelled); otherwise resolve the missing_at_psp exception.
transaction.risk.approved v1 no no The risk stage approved the transaction (rules evaluated, none decided against it). None.
transaction.risk.challenged v1 no no The risk stage asked for a step-up of the customer's authentication; the transaction proceeds with the connector told so. None.
transaction.risk.declined v1 no no The risk stage declined the transaction before any provider call. The caller was told only that it was declined. None, unless the rule was wrong: adjust the rules or the block list.
transaction.risk.review_decided v1 no no A held transaction was approved (then routed and submitted), declined (failed) or expired by its review timeout. None.
transaction.risk.review_required v1 no no The risk stage held the transaction for a compliance officer; it stays pending until reviewed or it expires. Review it on /admin/risk before the review timeout.
transaction.stage.await_confirmation.cancelled v1 no no The payment was cancelled before it completed. None.
transaction.stage.await_confirmation.expired v1 no no The payment expired before the PSP confirmed it. None.
transaction.stage.await_confirmation.failed v1 no no The PSP reported the payment failed. None.
transaction.stage.await_confirmation.succeeded v1 no no The PSP confirmed the payment. None.
transaction.stage.enrich_validate.passed v1 no no The request passed validation (account, amount, currency, pilot limits). None.
transaction.stage.enrich_validate.rejected v1 no no The request was refused at validation: outside every eligible PSP's capability matrix or limits. None.
transaction.stage.notify.queued v1 no no Webhook deliveries were queued for the merchant. None.
transaction.stage.notify.skipped v1 no no The merchant has no webhook endpoint for this mode. None.
transaction.stage.post.posted v1 no no The ledger entries for the payment were written. None.
transaction.stage.receive.accepted v1 no no The request was received and accepted for processing. None.
transaction.stage.reconcile.exception v1 no no A line of the PSP statement disagrees with the payment. Work the exception on /admin/reconciliation.
transaction.stage.reconcile.matched v1 no no The payment matched a line of the PSP statement. None.
transaction.stage.risk.approved v1 no no The risk engine approved the transaction. None.
transaction.stage.risk.challenged v1 no no The risk engine asked for a customer challenge. None.
transaction.stage.risk.declined v1 no no The risk engine declined the transaction. None.
transaction.stage.risk.review_required v1 no no The risk engine held the transaction for a compliance review. None.
transaction.stage.risk.skipped v1 no no No risk engine evaluated the transaction. None.
transaction.stage.route.no_route v1 no no No eligible PSP could take the transaction. Check the routing rules and PSP availability.
transaction.stage.route.routed v1 no no A PSP was chosen for the transaction. None.
transaction.stage.settle.settled v1 no no The PSP settled the payment. None.
transaction.stage.submit.accepted v1 no no The PSP accepted the request; the outcome may still be pending. None.
transaction.stage.submit.declined v1 no no The PSP declined the request. None.
transaction.stage.submit.error v1 no no The request could not be sent to the PSP. None.
transaction.stage.submit.requires_action v1 no no The PSP needs the customer to act (redirect, PIN, OTP). None.
transaction.stage.submit.soft_declined v1 no no The PSP declined the request with a retryable (soft) decline. None.
transaction.stage.submit.unknown v1 no no The PSP answered with nothing usable, so the outcome is unknown. None: a status query resolves it.
transaction.status_query.resolved v1 no yes An unknown outcome was resolved (PSP-9.3): a status query got the PSP's final answer, or the payment was settled meanwhile (a callback, the settlement sync). The original request was never resent; kulpay_ref is the one reference every attempt carried. None: the payment moves to the status found.
transaction.status_query.sent v1 no no KulPay asked the PSP for the status of a payment of unknown outcome. None.
transaction.transition.rejected v1 no no A status change the transition table does not allow was refused: the payment keeps its state. When the refused change and the recorded state disagree about whether money moved, a payment exception is raised. None, unless money_may_have_moved: then resolve the payment exception.

payment.authorized

The funds are held (authorised) and the payment waits for a capture or a void. An authorisation expires if it is not captured in time. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.authorized/v1",
  "title": "payment.authorized",
  "description": "The funds are held (authorised) and the payment waits for a capture or a void. An authorisation expires if it is not captured in time.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": true,
    "terminal": false,
    "act": "Capture it (POST /v1/payments/{id}/capture) or void it."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.cancelled

The payment was cancelled before settling, by the customer or an operator. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.cancelled/v2",
  "title": "payment.cancelled",
  "description": "The payment was cancelled before settling, by the customer or an operator.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": true,
    "terminal": true,
    "act": "Release any reservation you made. No money moved."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.capture_requested

A capture was claimed and sent to the provider. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.capture_requested/v2",
  "title": "payment.capture_requested",
  "description": "A capture was claimed and sent to the provider.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Progress only."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.created

A payment was created and is waiting for a method or for the provider. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.created/v2",
  "title": "payment.created",
  "description": "A payment was created and is waiting for a method or for the provider.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Nothing to do yet; wait for a later event."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.disputed

The payer disputed the payment (a chargeback or provider dispute). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.disputed/v2",
  "title": "payment.disputed",
  "description": "The payer disputed the payment (a chargeback or provider dispute).",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": true,
    "terminal": false,
    "act": "Hold fulfilment where you can and prepare evidence."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.evidence_recorded

Reconciliation evidence (depositor, bank, reference) was recorded on a manual payment. Values are masked. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.evidence_recorded/v1",
  "title": "payment.evidence_recorded",
  "description": "Reconciliation evidence (depositor, bank, reference) was recorded on a manual payment. Values are masked.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Progress only."
  },
  "type": "object",
  "required": [
    "payment_id",
    "fields"
  ],
  "properties": {
    "payment_id": {
      "type": "string",
      "minLength": 1
    },
    "fields": {
      "type": "object",
      "additionalProperties": {
        "type": "string",
        "x-sensitive": "mask"
      }
    }
  },
  "additionalProperties": false
}

payment.expired

The customer never completed the payment inside its window. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.expired/v2",
  "title": "payment.expired",
  "description": "The customer never completed the payment inside its window.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": true,
    "terminal": true,
    "act": "Same as cancelled: release, no money moved."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.failed

The payment was declined or could not be completed. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.failed/v2",
  "title": "payment.failed",
  "description": "The payment was declined or could not be completed.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": true,
    "terminal": true,
    "act": "Do not fulfil. failure_code says why; let the customer retry."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.method_selected

The customer chose a payment method on the hosted checkout. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.method_selected/v2",
  "title": "payment.method_selected",
  "description": "The customer chose a payment method on the hosted checkout.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Progress only."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.partially_refunded

Part of the payment was refunded. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.partially_refunded/v2",
  "title": "payment.partially_refunded",
  "description": "Part of the payment was refunded.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Informational; act on refund.succeeded."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.pending

The payment is waiting on the provider or the customer. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.pending/v2",
  "title": "payment.pending",
  "description": "The payment is waiting on the provider or the customer.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Progress only. Wait for a terminal event."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.processing

The provider accepted the request and is working on it. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.processing/v2",
  "title": "payment.processing",
  "description": "The provider accepted the request and is working on it.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": true,
    "terminal": false,
    "act": "Progress only. Show the customer that it is in flight; wait for a terminal event."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.provider_call_failed

A claimed provider call (capture or void) failed; the claim is released so the operation can be retried. The payment's status is unchanged. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.provider_call_failed/v1",
  "title": "payment.provider_call_failed",
  "description": "A claimed provider call (capture or void) failed; the claim is released so the operation can be retried. The payment's status is unchanged.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None; retry the operation if it is still wanted."
  },
  "type": "object",
  "required": [
    "payment_id",
    "operation",
    "status"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "operation": {
      "enum": [
        "capture",
        "void"
      ]
    },
    "status": {
      "type": "string"
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

payment.refunded

The payment was fully refunded. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.refunded/v2",
  "title": "payment.refunded",
  "description": "The payment was fully refunded.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Informational; act on refund.succeeded."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.requires_action

The customer must do something off-page: approve a USSD prompt, enter an OTP, complete 3DS. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.requires_action/v2",
  "title": "payment.requires_action",
  "description": "The customer must do something off-page: approve a USSD prompt, enter an OTP, complete 3DS.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": true,
    "terminal": false,
    "act": "Progress only. The hosted checkout drives this; if you built your own UI, read next_action."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.settlement_changed

The settlement state of a succeeded payment changed: unsettled, settled by the provider, reconciled against its statement. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.settlement_changed/v1",
  "title": "payment.settlement_changed",
  "description": "The settlement state of a succeeded payment changed: unsettled, settled by the provider, reconciled against its statement.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "payment_id",
    "from",
    "to"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "enum": [
        "unsettled",
        "settled",
        "reconciled"
      ]
    }
  },
  "additionalProperties": false
}

payment.succeeded

The payment settled and the money is captured. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.succeeded/v2",
  "title": "payment.succeeded",
  "description": "The payment settled and the money is captured.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": true,
    "terminal": true,
    "act": "Fulfil the order. This is the only event that means you have been paid."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.updated

Payment details changed without a status change: a provider reference, a next action or a checkout link was recorded. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.updated/v2",
  "title": "payment.updated",
  "description": "Payment details changed without a status change: a provider reference, a next action or a checkout link was recorded.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Progress only."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

payment.void_requested

A void was claimed and sent to the provider. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.void_requested/v2",
  "title": "payment.void_requested",
  "description": "A void was claimed and sent to the provider.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Progress only."
  },
  "$ref": "kulpay://objects/payment.v2.json"
}

psp.code.unmapped

A provider answered with a code that has no mapping. It is treated as unknown, never as a success or a failure: the payment (or refund) stays unsettled until a status query or reconciliation decides it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.code.unmapped/v1",
  "title": "psp.code.unmapped",
  "description": "A provider answered with a code that has no mapping. It is treated as unknown, never as a success or a failure: the payment (or refund) stays unsettled until a status query or reconciliation decides it.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Add the code to the provider's mapping on /admin/code-mappings."
  },
  "type": "object",
  "required": [
    "payment_id",
    "provider",
    "raw_code",
    "operation"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "provider": {
      "type": "string"
    },
    "raw_code": {
      "type": "string"
    },
    "operation": {
      "enum": [
        "authorize",
        "status_query",
        "callback",
        "dispute",
        "refund"
      ]
    },
    "message": {
      "type": "string"
    },
    "payment_status": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

reconciliation.payment_exception.raised

A payment's recorded state and a later report contradict each other about money (a success after a failure, a failure after a success). An operator decides what really happened. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reconciliation.payment_exception.raised/v1",
  "title": "reconciliation.payment_exception.raised",
  "description": "A payment's recorded state and a later report contradict each other about money (a success after a failure, a failure after a success). An operator decides what really happened.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Check with the provider and resolve it on /admin/reconciliation."
  },
  "type": "object",
  "required": [
    "payment_id",
    "exception_id",
    "from",
    "to"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "exception_id": {
      "type": "string"
    },
    "kind": {
      "type": "string"
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "detail": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

reconciliation.payment_exception.resolved

An operator resolved a payment exception. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reconciliation.payment_exception.resolved/v1",
  "title": "reconciliation.payment_exception.resolved",
  "description": "An operator resolved a payment exception.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "payment_id",
    "exception_id",
    "resolution"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "exception_id": {
      "type": "string"
    },
    "resolution": {
      "type": "string"
    },
    "resolved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

retry.attempted

One attempt of an outbound operation under a retry policy (PSP-9.2): its number, latency, result and what the engine decided next (retry, failover, status_query, dlq, fail, resolved). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/retry.attempted/v1",
  "title": "retry.attempted",
  "description": "One attempt of an outbound operation under a retry policy (PSP-9.2): its number, latency, result and what the engine decided next (retry, failover, status_query, dlq, fail, resolved).",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "payment_id",
    "operation",
    "pattern",
    "attempt",
    "result",
    "next"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "operation": {
      "enum": [
        "submit",
        "status_query"
      ]
    },
    "pattern": {
      "type": "string"
    },
    "attempt": {
      "type": "integer"
    },
    "latency_ms": {
      "type": "integer"
    },
    "result": {
      "type": "string"
    },
    "next": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

retry.exhausted

An outbound operation used up its retry policy (PSP-9.2) and took the policy's final action: fail, a reversal or an exception after status queries, or nothing left to fail over to. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/retry.exhausted/v1",
  "title": "retry.exhausted",
  "description": "An outbound operation used up its retry policy (PSP-9.2) and took the policy's final action: fail, a reversal or an exception after status queries, or nothing left to fail over to.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": true,
    "act": "For status queries: the reversal or the missing_at_psp exception that follows. For submissions: the payment ends with its failure."
  },
  "type": "object",
  "required": [
    "payment_id",
    "operation",
    "pattern",
    "attempts",
    "action"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "operation": {
      "enum": [
        "submit",
        "status_query"
      ]
    },
    "pattern": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    },
    "action": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

retry.scheduled

An outbound operation's next attempt was scheduled by the PSP's retry policy (PSP-9.2), and stored in the database: a restart loses none. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/retry.scheduled/v1",
  "title": "retry.scheduled",
  "description": "An outbound operation's next attempt was scheduled by the PSP's retry policy (PSP-9.2), and stored in the database: a restart loses none.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "payment_id",
    "operation",
    "pattern",
    "attempt",
    "at"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "operation": {
      "enum": [
        "submit",
        "status_query"
      ]
    },
    "pattern": {
      "type": "string"
    },
    "attempt": {
      "type": "integer"
    },
    "at": {
      "type": "string",
      "format": "date-time"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

transaction.card.tokenized

A card payment carries a card the acquirer tokenised (PSP-12.1): KulPay received a token or payment method ID with the display fields only (brand, last 4 digits, expiry), never the card number or CVV. Recorded with the payment it pays for. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.card.tokenized/v1",
  "title": "transaction.card.tokenized",
  "description": "A card payment carries a card the acquirer tokenised (PSP-12.1): KulPay received a token or payment method ID with the display fields only (brand, last 4 digits, expiry), never the card number or CVV. Recorded with the payment it pays for.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None. Evidence for PCI scope: the card was collected in the acquirer's hosted fields or tokenised by the merchant's integration."
  },
  "type": "object",
  "required": [
    "payment_id",
    "token_type",
    "channel"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "token_type": {
      "description": "payment_method (an acquirer payment method ID such as pm_...), token (an acquirer card token such as tok_...) or test (a documented test card's token, test mode only).",
      "enum": [
        "payment_method",
        "token",
        "test"
      ]
    },
    "channel": {
      "description": "hosted_checkout (the acquirer's hosted fields on KulPay's checkout) or api (a token the merchant's integration obtained).",
      "enum": [
        "hosted_checkout",
        "api"
      ]
    },
    "brand": {
      "type": "string"
    },
    "last4": {
      "type": "string",
      "pattern": "^[0-9]{4}$"
    },
    "exp_month": {
      "type": "integer"
    },
    "exp_year": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

transaction.failover.attempted

A payment moved to the next PSP of its routing after a clean failure: a soft decline, or an error before the request was sent (connection refused, DNS failure, an open circuit). Never after the PSP may have accepted it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.failover.attempted/v1",
  "title": "transaction.failover.attempted",
  "description": "A payment moved to the next PSP of its routing after a clean failure: a soft decline, or an error before the request was sent (connection refused, DNS failure, an open circuit). Never after the PSP may have accepted it.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None. Frequent failovers away from one PSP point at its health or its decline rate."
  },
  "type": "object",
  "required": [
    "payment_id",
    "from_psp",
    "to_psp",
    "reason",
    "failed_attempt",
    "failover"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "from_psp": {
      "type": "string"
    },
    "to_psp": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "soft_decline",
        "pre_submission_error",
        "circuit_open"
      ]
    },
    "failed_attempt": {
      "type": "integer"
    },
    "failover": {
      "type": "integer"
    },
    "max_failovers": {
      "type": "integer"
    },
    "raw_code": {
      "type": "string"
    },
    "mapped_code": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

transaction.outcome.unknown

A payment's PSP result is unknown: the answer never came back after sending (timeout, dropped connection), it stayed in flight past its window, or the PSP declares no callback for the transaction type (its orientation profile), so status queries start at once. It stays processing while status queries find out what happened. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.outcome.unknown/v2",
  "title": "transaction.outcome.unknown",
  "description": "A payment's PSP result is unknown: the answer never came back after sending (timeout, dropped connection), it stayed in flight past its window, or the PSP declares no callback for the transaction type (its orientation profile), so status queries start at once. It stays processing while status queries find out what happened.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None: status queries run on a schedule; a reversal or an exception follows if the window closes."
  },
  "type": "object",
  "required": [
    "payment_id",
    "reason"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "connector": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "submit_unknown",
        "stale_pending",
        "no_callback"
      ]
    },
    "detail": {
      "type": "string"
    },
    "window_until": {
      "type": "string",
      "format": "date-time"
    },
    "next_query_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

transaction.rejected

A request was refused before any provider call (PSP-5.3): no eligible PSP's capability matrix allows it, its limits have no room, or no PSP is available. No payment was created unless the request belonged to a checkout. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.rejected/v1",
  "title": "transaction.rejected",
  "description": "A request was refused before any provider call (PSP-5.3): no eligible PSP's capability matrix allows it, its limits have no room, or no PSP is available. No payment was created unless the request belonged to a checkout.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None for the platform; the caller got 422 (not_supported, limit_exceeded) or 503 (no_route). Frequent refusals for one rule may mean a capability matrix or routing rule needs a change."
  },
  "type": "object",
  "required": [
    "rejection_id",
    "stage",
    "code",
    "rule",
    "message"
  ],
  "properties": {
    "rejection_id": {
      "type": "string"
    },
    "stage": {
      "enum": [
        "enrich_validate",
        "route"
      ]
    },
    "code": {
      "enum": [
        "not_supported",
        "limit_exceeded",
        "no_route"
      ]
    },
    "rule": {
      "type": "string"
    },
    "message": {
      "type": "string"
    },
    "method": {
      "type": "string"
    },
    "transaction_type": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "country": {
      "type": "string"
    },
    "amount": {
      "type": "string"
    },
    "candidates": {
      "type": [
        "array",
        "null"
      ]
    }
  },
  "additionalProperties": false
}

transaction.reversal.requested

The status window of a payment of unknown outcome closed without an answer; KulPay asked the PSP to reverse it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.reversal.requested/v1",
  "title": "transaction.reversal.requested",
  "description": "The status window of a payment of unknown outcome closed without an answer; KulPay asked the PSP to reverse it.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None if it is reversed (the payment ends cancelled); otherwise resolve the missing_at_psp exception."
  },
  "type": "object",
  "required": [
    "payment_id",
    "reason"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "gateway_ref": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

transaction.risk.approved

The risk stage approved the transaction (rules evaluated, none decided against it). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.risk.approved/v1",
  "title": "transaction.risk.approved",
  "description": "The risk stage approved the transaction (rules evaluated, none decided against it).",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "payment_id",
    "outcome",
    "rules"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "outcome": {
      "enum": [
        "approve",
        "review",
        "decline",
        "challenge"
      ]
    },
    "rules": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "reasons": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "engine": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

transaction.risk.challenged

The risk stage asked for a step-up of the customer's authentication; the transaction proceeds with the connector told so. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.risk.challenged/v1",
  "title": "transaction.risk.challenged",
  "description": "The risk stage asked for a step-up of the customer's authentication; the transaction proceeds with the connector told so.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "payment_id",
    "outcome",
    "rules"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "outcome": {
      "enum": [
        "approve",
        "review",
        "decline",
        "challenge"
      ]
    },
    "rules": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "reasons": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "engine": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

transaction.risk.declined

The risk stage declined the transaction before any provider call. The caller was told only that it was declined. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.risk.declined/v1",
  "title": "transaction.risk.declined",
  "description": "The risk stage declined the transaction before any provider call. The caller was told only that it was declined.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None, unless the rule was wrong: adjust the rules or the block list."
  },
  "type": "object",
  "required": [
    "payment_id",
    "outcome",
    "rules"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "outcome": {
      "enum": [
        "approve",
        "review",
        "decline",
        "challenge"
      ]
    },
    "rules": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "reasons": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "engine": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

transaction.risk.review_decided

A held transaction was approved (then routed and submitted), declined (failed) or expired by its review timeout. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.risk.review_decided/v1",
  "title": "transaction.risk.review_decided",
  "description": "A held transaction was approved (then routed and submitted), declined (failed) or expired by its review timeout.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "payment_id",
    "review_id",
    "decision"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "review_id": {
      "type": "string"
    },
    "decision": {
      "enum": [
        "approved",
        "declined",
        "expired"
      ]
    },
    "reason": {
      "type": "string"
    },
    "decided_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

transaction.risk.review_required

The risk stage held the transaction for a compliance officer; it stays pending until reviewed or it expires. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.risk.review_required/v1",
  "title": "transaction.risk.review_required",
  "description": "The risk stage held the transaction for a compliance officer; it stays pending until reviewed or it expires.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Review it on /admin/risk before the review timeout."
  },
  "type": "object",
  "required": [
    "payment_id",
    "outcome",
    "rules"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "outcome": {
      "enum": [
        "approve",
        "review",
        "decline",
        "challenge"
      ]
    },
    "rules": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "reasons": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "engine": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

transaction.stage.await_confirmation.cancelled

The payment was cancelled before it completed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.await_confirmation.cancelled/v1",
  "title": "transaction.stage.await_confirmation.cancelled",
  "description": "The payment was cancelled before it completed.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.await_confirmation.expired

The payment expired before the PSP confirmed it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.await_confirmation.expired/v1",
  "title": "transaction.stage.await_confirmation.expired",
  "description": "The payment expired before the PSP confirmed it.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.await_confirmation.failed

The PSP reported the payment failed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.await_confirmation.failed/v1",
  "title": "transaction.stage.await_confirmation.failed",
  "description": "The PSP reported the payment failed.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.await_confirmation.succeeded

The PSP confirmed the payment. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.await_confirmation.succeeded/v1",
  "title": "transaction.stage.await_confirmation.succeeded",
  "description": "The PSP confirmed the payment.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.enrich_validate.passed

The request passed validation (account, amount, currency, pilot limits). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.enrich_validate.passed/v1",
  "title": "transaction.stage.enrich_validate.passed",
  "description": "The request passed validation (account, amount, currency, pilot limits).",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.enrich_validate.rejected

The request was refused at validation: outside every eligible PSP's capability matrix or limits. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.enrich_validate.rejected/v1",
  "title": "transaction.stage.enrich_validate.rejected",
  "description": "The request was refused at validation: outside every eligible PSP's capability matrix or limits.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.notify.queued

Webhook deliveries were queued for the merchant. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.notify.queued/v1",
  "title": "transaction.stage.notify.queued",
  "description": "Webhook deliveries were queued for the merchant.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.notify.skipped

The merchant has no webhook endpoint for this mode. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.notify.skipped/v1",
  "title": "transaction.stage.notify.skipped",
  "description": "The merchant has no webhook endpoint for this mode.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.post.posted

The ledger entries for the payment were written. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.post.posted/v1",
  "title": "transaction.stage.post.posted",
  "description": "The ledger entries for the payment were written.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.receive.accepted

The request was received and accepted for processing. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.receive.accepted/v1",
  "title": "transaction.stage.receive.accepted",
  "description": "The request was received and accepted for processing.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.reconcile.exception

A line of the PSP statement disagrees with the payment. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.reconcile.exception/v1",
  "title": "transaction.stage.reconcile.exception",
  "description": "A line of the PSP statement disagrees with the payment.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Work the exception on /admin/reconciliation."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.reconcile.matched

The payment matched a line of the PSP statement. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.reconcile.matched/v1",
  "title": "transaction.stage.reconcile.matched",
  "description": "The payment matched a line of the PSP statement.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.risk.approved

The risk engine approved the transaction. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.risk.approved/v1",
  "title": "transaction.stage.risk.approved",
  "description": "The risk engine approved the transaction.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.risk.challenged

The risk engine asked for a customer challenge. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.risk.challenged/v1",
  "title": "transaction.stage.risk.challenged",
  "description": "The risk engine asked for a customer challenge.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.risk.declined

The risk engine declined the transaction. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.risk.declined/v1",
  "title": "transaction.stage.risk.declined",
  "description": "The risk engine declined the transaction.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.risk.review_required

The risk engine held the transaction for a compliance review. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.risk.review_required/v1",
  "title": "transaction.stage.risk.review_required",
  "description": "The risk engine held the transaction for a compliance review.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.risk.skipped

No risk engine evaluated the transaction. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.risk.skipped/v1",
  "title": "transaction.stage.risk.skipped",
  "description": "No risk engine evaluated the transaction.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.route.no_route

No eligible PSP could take the transaction. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.route.no_route/v1",
  "title": "transaction.stage.route.no_route",
  "description": "No eligible PSP could take the transaction.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "Check the routing rules and PSP availability."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.route.routed

A PSP was chosen for the transaction. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.route.routed/v1",
  "title": "transaction.stage.route.routed",
  "description": "A PSP was chosen for the transaction.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.settle.settled

The PSP settled the payment. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.settle.settled/v1",
  "title": "transaction.stage.settle.settled",
  "description": "The PSP settled the payment.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.submit.accepted

The PSP accepted the request; the outcome may still be pending. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.submit.accepted/v1",
  "title": "transaction.stage.submit.accepted",
  "description": "The PSP accepted the request; the outcome may still be pending.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.submit.declined

The PSP declined the request. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.submit.declined/v1",
  "title": "transaction.stage.submit.declined",
  "description": "The PSP declined the request.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.submit.error

The request could not be sent to the PSP. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.submit.error/v1",
  "title": "transaction.stage.submit.error",
  "description": "The request could not be sent to the PSP.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.submit.requires_action

The PSP needs the customer to act (redirect, PIN, OTP). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.submit.requires_action/v1",
  "title": "transaction.stage.submit.requires_action",
  "description": "The PSP needs the customer to act (redirect, PIN, OTP).",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.submit.soft_declined

The PSP declined the request with a retryable (soft) decline. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.submit.soft_declined/v1",
  "title": "transaction.stage.submit.soft_declined",
  "description": "The PSP declined the request with a retryable (soft) decline.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.stage.submit.unknown

The PSP answered with nothing usable, so the outcome is unknown. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.stage.submit.unknown/v1",
  "title": "transaction.stage.submit.unknown",
  "description": "The PSP answered with nothing usable, so the outcome is unknown.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None: a status query resolves it."
  },
  "$ref": "kulpay://objects/transaction_stage.json"
}

transaction.status_query.resolved

An unknown outcome was resolved (PSP-9.3): a status query got the PSP's final answer, or the payment was settled meanwhile (a callback, the settlement sync). The original request was never resent; kulpay_ref is the one reference every attempt carried. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.status_query.resolved/v1",
  "title": "transaction.status_query.resolved",
  "description": "An unknown outcome was resolved (PSP-9.3): a status query got the PSP's final answer, or the payment was settled meanwhile (a callback, the settlement sync). The original request was never resent; kulpay_ref is the one reference every attempt carried.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": true,
    "act": "None: the payment moves to the status found."
  },
  "type": "object",
  "required": [
    "payment_id",
    "kulpay_ref",
    "result",
    "attempts",
    "by"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "kulpay_ref": {
      "type": "string"
    },
    "result": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    },
    "by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

transaction.status_query.sent

KulPay asked the PSP for the status of a payment of unknown outcome. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.status_query.sent/v1",
  "title": "transaction.status_query.sent",
  "description": "KulPay asked the PSP for the status of a payment of unknown outcome.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "payment_id",
    "attempt",
    "by",
    "result"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "attempt": {
      "type": "integer"
    },
    "by": {
      "enum": [
        "connector_ref",
        "reference",
        "none",
        "reversal",
        "settled_elsewhere"
      ]
    },
    "result": {
      "type": "string"
    },
    "window_until": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

transaction.transition.rejected

A status change the transition table does not allow was refused: the payment keeps its state. When the refused change and the recorded state disagree about whether money moved, a payment exception is raised. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.transition.rejected/v1",
  "title": "transaction.transition.rejected",
  "description": "A status change the transition table does not allow was refused: the payment keeps its state. When the refused change and the recorded state disagree about whether money moved, a payment exception is raised.",
  "x-kulpay": {
    "aggregate": "payment",
    "webhook": false,
    "terminal": false,
    "act": "None, unless money_may_have_moved: then resolve the payment exception."
  },
  "type": "object",
  "required": [
    "payment_id",
    "from",
    "to",
    "cause"
  ],
  "properties": {
    "payment_id": {
      "type": "string"
    },
    "from": {
      "enum": [
        "created",
        "pending",
        "requires_action",
        "processing",
        "authorized",
        "succeeded",
        "failed",
        "cancelled",
        "expired",
        "disputed",
        "refunded",
        "partially_refunded"
      ]
    },
    "to": {
      "enum": [
        "created",
        "pending",
        "requires_action",
        "processing",
        "authorized",
        "succeeded",
        "failed",
        "cancelled",
        "expired",
        "disputed",
        "refunded",
        "partially_refunded"
      ]
    },
    "cause": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    },
    "money_may_have_moved": {
      "type": "boolean"
    },
    "provider_code": {
      "type": "string"
    }
  },
  "additionalProperties": false
}
EventWebhookTerminalFires whenWhat to do
payment_link.created v1 no no A payment link was created. Audit record. Compare before and after.
payment_link.deactivated v1 no no A payment link was deactivated. Audit record. Compare before and after.
payment_link.redeemed v1 no no A customer claimed one use of a payment link. Audit record. Compare before and after.
payment_link.released v1 no no A claimed use of a payment link was returned because the payment could not be created. Audit record. Compare before and after.

payment_link.created

A payment link was created. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment_link.created/v1",
  "title": "payment_link.created",
  "description": "A payment link was created.",
  "x-kulpay": {
    "aggregate": "payment_link",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/payment_link.json"
    },
    "after": {
      "$ref": "kulpay://objects/payment_link.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

payment_link.deactivated

A payment link was deactivated. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment_link.deactivated/v1",
  "title": "payment_link.deactivated",
  "description": "A payment link was deactivated.",
  "x-kulpay": {
    "aggregate": "payment_link",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/payment_link.json"
    },
    "after": {
      "$ref": "kulpay://objects/payment_link.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

payment_link.redeemed

A customer claimed one use of a payment link. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment_link.redeemed/v1",
  "title": "payment_link.redeemed",
  "description": "A customer claimed one use of a payment link.",
  "x-kulpay": {
    "aggregate": "payment_link",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/payment_link.json"
    },
    "after": {
      "$ref": "kulpay://objects/payment_link.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

payment_link.released

A claimed use of a payment link was returned because the payment could not be created. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment_link.released/v1",
  "title": "payment_link.released",
  "description": "A claimed use of a payment link was returned because the payment could not be created.",
  "x-kulpay": {
    "aggregate": "payment_link",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/payment_link.json"
    },
    "after": {
      "$ref": "kulpay://objects/payment_link.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

payout events

EventWebhookTerminalFires whenWhat to do
payout.approved v1 no no A second person approved a payout above the approval threshold; it is submitted next. None.
payout.created v1 no no A payout was created and its amount debited from the available balance; it is submitted, or waits for approval. None; approve it if it needs approval.
payout.failed v1 yes yes The payout failed (declined, not sent, or rejected by an approver); its amount is back in the available balance. Check failure_code; correct the beneficiary or retry with a new payout.
payout.returned v1 yes yes A paid payout came back (the beneficiary account rejected it after the fact); its amount is back in the available balance. Contact the beneficiary; pay again with a new payout.
payout.submitted v1 no no A payout was sent to its PSP. None.
payout.succeeded v1 yes yes The beneficiary was paid. None. Mark the obligation paid.

payout.approved

A second person approved a payout above the approval threshold; it is submitted next. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.approved/v1",
  "title": "payout.approved",
  "description": "A second person approved a payout above the approval threshold; it is submitted next.",
  "x-kulpay": {
    "aggregate": "payout",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/payout.json"
}

payout.created

A payout was created and its amount debited from the available balance; it is submitted, or waits for approval. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.created/v1",
  "title": "payout.created",
  "description": "A payout was created and its amount debited from the available balance; it is submitted, or waits for approval.",
  "x-kulpay": {
    "aggregate": "payout",
    "webhook": false,
    "terminal": false,
    "act": "None; approve it if it needs approval."
  },
  "$ref": "kulpay://objects/payout.json"
}

payout.failed

The payout failed (declined, not sent, or rejected by an approver); its amount is back in the available balance. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.failed/v1",
  "title": "payout.failed",
  "description": "The payout failed (declined, not sent, or rejected by an approver); its amount is back in the available balance.",
  "x-kulpay": {
    "aggregate": "payout",
    "webhook": true,
    "terminal": true,
    "act": "Check failure_code; correct the beneficiary or retry with a new payout."
  },
  "$ref": "kulpay://objects/payout.json"
}

payout.returned

A paid payout came back (the beneficiary account rejected it after the fact); its amount is back in the available balance. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.returned/v1",
  "title": "payout.returned",
  "description": "A paid payout came back (the beneficiary account rejected it after the fact); its amount is back in the available balance.",
  "x-kulpay": {
    "aggregate": "payout",
    "webhook": true,
    "terminal": true,
    "act": "Contact the beneficiary; pay again with a new payout."
  },
  "$ref": "kulpay://objects/payout.json"
}

payout.submitted

A payout was sent to its PSP. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.submitted/v1",
  "title": "payout.submitted",
  "description": "A payout was sent to its PSP.",
  "x-kulpay": {
    "aggregate": "payout",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "$ref": "kulpay://objects/payout.json"
}

payout.succeeded

The beneficiary was paid. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.succeeded/v1",
  "title": "payout.succeeded",
  "description": "The beneficiary was paid.",
  "x-kulpay": {
    "aggregate": "payout",
    "webhook": true,
    "terminal": true,
    "act": "None. Mark the obligation paid."
  },
  "$ref": "kulpay://objects/payout.json"
}

payout_batch events

EventWebhookTerminalFires whenWhat to do
payout.batch.acknowledged v1 no no The bank's pain.002 answered a batch: every transfer accepted, or some rejected (status partially_rejected; the rejected payouts failed and their money came back). (F2-1) Treasury watches the rejected count; the accepted transfers settle by camt.054.
payout.batch.closed v1 no no Bank payouts accepted before a cut-off were closed into a batch; its pain.001 is built and kept, ready to send. (F2-1) None: the batch is sent to the sponsor bank.
payout.batch.rejected v1 no yes The bank rejected every transfer of a batch (pain.002); each payout failed and its money came back. (F2-1) Treasury checks the reason (a file or account problem) before the payouts are created again.
payout.batch.released v1 no no A closed batch was released for sending: a second person approved it (live), or, in test mode, the system released it at once (released_by system:test_mode) (F2-2). None: the next run of the bank job sends the pain.001.
payout.batch.sent v1 no no A batch's pain.001 was delivered to the sponsor bank. (F2-1) None: the bank answers with a pain.002, then camt.054 debits.
payout.batch.settled v1 no yes Every transfer of a batch is final: the bank's camt.054 debited the accepted ones (some may since have been returned). (F2-1) None.

payout.batch.acknowledged

The bank's pain.002 answered a batch: every transfer accepted, or some rejected (status partially_rejected; the rejected payouts failed and their money came back). (F2-1) Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.batch.acknowledged/v1",
  "title": "payout.batch.acknowledged",
  "description": "The bank's pain.002 answered a batch: every transfer accepted, or some rejected (status partially_rejected; the rejected payouts failed and their money came back). (F2-1)",
  "x-kulpay": {
    "aggregate": "payout_batch",
    "webhook": false,
    "terminal": false,
    "act": "Treasury watches the rejected count; the accepted transfers settle by camt.054."
  },
  "type": "object",
  "required": [
    "batch_id",
    "msg_id",
    "currency",
    "count",
    "total_minor",
    "status"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "msg_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "status": {
      "enum": [
        "open",
        "closed",
        "sent",
        "acknowledged",
        "partially_rejected",
        "rejected",
        "settled"
      ]
    },
    "accepted": {
      "type": "integer"
    },
    "rejected": {
      "type": "integer"
    },
    "settled": {
      "type": "integer"
    },
    "execution_date": {
      "type": "string",
      "format": "date"
    }
  },
  "additionalProperties": false
}

payout.batch.closed

Bank payouts accepted before a cut-off were closed into a batch; its pain.001 is built and kept, ready to send. (F2-1) Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.batch.closed/v1",
  "title": "payout.batch.closed",
  "description": "Bank payouts accepted before a cut-off were closed into a batch; its pain.001 is built and kept, ready to send. (F2-1)",
  "x-kulpay": {
    "aggregate": "payout_batch",
    "webhook": false,
    "terminal": false,
    "act": "None: the batch is sent to the sponsor bank."
  },
  "type": "object",
  "required": [
    "batch_id",
    "msg_id",
    "currency",
    "count",
    "total_minor",
    "status"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "msg_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "status": {
      "enum": [
        "open",
        "closed",
        "sent",
        "acknowledged",
        "partially_rejected",
        "rejected",
        "settled"
      ]
    },
    "accepted": {
      "type": "integer"
    },
    "rejected": {
      "type": "integer"
    },
    "settled": {
      "type": "integer"
    },
    "execution_date": {
      "type": "string",
      "format": "date"
    }
  },
  "additionalProperties": false
}

payout.batch.rejected

The bank rejected every transfer of a batch (pain.002); each payout failed and its money came back. (F2-1) Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.batch.rejected/v1",
  "title": "payout.batch.rejected",
  "description": "The bank rejected every transfer of a batch (pain.002); each payout failed and its money came back. (F2-1)",
  "x-kulpay": {
    "aggregate": "payout_batch",
    "webhook": false,
    "terminal": true,
    "act": "Treasury checks the reason (a file or account problem) before the payouts are created again."
  },
  "type": "object",
  "required": [
    "batch_id",
    "msg_id",
    "currency",
    "count",
    "total_minor",
    "status"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "msg_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "status": {
      "enum": [
        "open",
        "closed",
        "sent",
        "acknowledged",
        "partially_rejected",
        "rejected",
        "settled"
      ]
    },
    "accepted": {
      "type": "integer"
    },
    "rejected": {
      "type": "integer"
    },
    "settled": {
      "type": "integer"
    },
    "execution_date": {
      "type": "string",
      "format": "date"
    }
  },
  "additionalProperties": false
}

payout.batch.released

A closed batch was released for sending: a second person approved it (live), or, in test mode, the system released it at once (released_by system:test_mode) (F2-2). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.batch.released/v1",
  "title": "payout.batch.released",
  "description": "A closed batch was released for sending: a second person approved it (live), or, in test mode, the system released it at once (released_by system:test_mode) (F2-2).",
  "x-kulpay": {
    "aggregate": "payout_batch",
    "webhook": false,
    "terminal": false,
    "act": "None: the next run of the bank job sends the pain.001."
  },
  "type": "object",
  "required": [
    "batch_id",
    "msg_id",
    "currency",
    "count",
    "total_minor",
    "status"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "msg_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "status": {
      "enum": [
        "released"
      ]
    },
    "accepted": {
      "type": "integer"
    },
    "rejected": {
      "type": "integer"
    },
    "settled": {
      "type": "integer"
    },
    "execution_date": {
      "type": "string",
      "format": "date"
    },
    "released_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

payout.batch.sent

A batch's pain.001 was delivered to the sponsor bank. (F2-1) Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.batch.sent/v1",
  "title": "payout.batch.sent",
  "description": "A batch's pain.001 was delivered to the sponsor bank. (F2-1)",
  "x-kulpay": {
    "aggregate": "payout_batch",
    "webhook": false,
    "terminal": false,
    "act": "None: the bank answers with a pain.002, then camt.054 debits."
  },
  "type": "object",
  "required": [
    "batch_id",
    "msg_id",
    "currency",
    "count",
    "total_minor",
    "status"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "msg_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "status": {
      "enum": [
        "open",
        "closed",
        "sent",
        "acknowledged",
        "partially_rejected",
        "rejected",
        "settled"
      ]
    },
    "accepted": {
      "type": "integer"
    },
    "rejected": {
      "type": "integer"
    },
    "settled": {
      "type": "integer"
    },
    "execution_date": {
      "type": "string",
      "format": "date"
    }
  },
  "additionalProperties": false
}

payout.batch.settled

Every transfer of a batch is final: the bank's camt.054 debited the accepted ones (some may since have been returned). (F2-1) Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payout.batch.settled/v1",
  "title": "payout.batch.settled",
  "description": "Every transfer of a batch is final: the bank's camt.054 debited the accepted ones (some may since have been returned). (F2-1)",
  "x-kulpay": {
    "aggregate": "payout_batch",
    "webhook": false,
    "terminal": true,
    "act": "None."
  },
  "type": "object",
  "required": [
    "batch_id",
    "msg_id",
    "currency",
    "count",
    "total_minor",
    "status"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "msg_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "total_minor": {
      "type": "integer"
    },
    "status": {
      "enum": [
        "open",
        "closed",
        "sent",
        "acknowledged",
        "partially_rejected",
        "rejected",
        "settled"
      ]
    },
    "accepted": {
      "type": "integer"
    },
    "rejected": {
      "type": "integer"
    },
    "settled": {
      "type": "integer"
    },
    "execution_date": {
      "type": "string",
      "format": "date"
    }
  },
  "additionalProperties": false
}

projection events

EventWebhookTerminalFires whenWhat to do
projection.replay.completed v1 no no A projection rebuild passed its row-count and checksum comparison and was swapped in. Operators only.
projection.replay.failed v1 no no A projection rebuild failed its comparison or errored; the live projection was left unchanged. Operators only.
projection.replay.started v1 no no An operator started rebuilding a projection from the event history. Operators only.

projection.replay.completed

A projection rebuild passed its row-count and checksum comparison and was swapped in. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/projection.replay.completed/v1",
  "title": "projection.replay.completed",
  "description": "A projection rebuild passed its row-count and checksum comparison and was swapped in.",
  "x-kulpay": {
    "aggregate": "projection",
    "webhook": false,
    "terminal": false,
    "act": "Operators only."
  },
  "type": "object",
  "required": [
    "projection",
    "replay_id",
    "from",
    "to"
  ],
  "properties": {
    "projection": {
      "type": "string",
      "minLength": 1
    },
    "replay_id": {
      "type": "string",
      "minLength": 1
    },
    "from": {
      "type": "string",
      "format": "date-time"
    },
    "to": {
      "type": "string",
      "format": "date-time"
    },
    "events_read": {
      "type": "integer",
      "minimum": 0
    },
    "rows_written": {
      "type": "integer",
      "minimum": 0
    },
    "checksum": {
      "type": "string"
    },
    "duration_ms": {
      "type": "integer",
      "minimum": 0
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

projection.replay.failed

A projection rebuild failed its comparison or errored; the live projection was left unchanged. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/projection.replay.failed/v1",
  "title": "projection.replay.failed",
  "description": "A projection rebuild failed its comparison or errored; the live projection was left unchanged.",
  "x-kulpay": {
    "aggregate": "projection",
    "webhook": false,
    "terminal": false,
    "act": "Operators only."
  },
  "type": "object",
  "required": [
    "projection",
    "replay_id",
    "from",
    "to"
  ],
  "properties": {
    "projection": {
      "type": "string",
      "minLength": 1
    },
    "replay_id": {
      "type": "string",
      "minLength": 1
    },
    "from": {
      "type": "string",
      "format": "date-time"
    },
    "to": {
      "type": "string",
      "format": "date-time"
    },
    "events_read": {
      "type": "integer",
      "minimum": 0
    },
    "rows_written": {
      "type": "integer",
      "minimum": 0
    },
    "checksum": {
      "type": "string"
    },
    "duration_ms": {
      "type": "integer",
      "minimum": 0
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

projection.replay.started

An operator started rebuilding a projection from the event history. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/projection.replay.started/v1",
  "title": "projection.replay.started",
  "description": "An operator started rebuilding a projection from the event history.",
  "x-kulpay": {
    "aggregate": "projection",
    "webhook": false,
    "terminal": false,
    "act": "Operators only."
  },
  "type": "object",
  "required": [
    "projection",
    "replay_id",
    "from",
    "to"
  ],
  "properties": {
    "projection": {
      "type": "string",
      "minLength": 1
    },
    "replay_id": {
      "type": "string",
      "minLength": 1
    },
    "from": {
      "type": "string",
      "format": "date-time"
    },
    "to": {
      "type": "string",
      "format": "date-time"
    },
    "events_read": {
      "type": "integer",
      "minimum": 0
    },
    "rows_written": {
      "type": "integer",
      "minimum": 0
    },
    "checksum": {
      "type": "string"
    },
    "duration_ms": {
      "type": "integer",
      "minimum": 0
    },
    "error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp_application events

EventWebhookTerminalFires whenWhat to do
psp.application.drafted v1 no no A PSP application draft was saved. None; the applicant may come back to it.
psp.application.submitted v2 no no A PSP application was submitted and a PSP record created in state applied. v2: the application carries the partner kind (psp or lsp) and an LSP its biller category. Start the KYB review.

psp.application.drafted

A PSP application draft was saved. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.application.drafted/v1",
  "title": "psp.application.drafted",
  "description": "A PSP application draft was saved.",
  "x-kulpay": {
    "aggregate": "psp_application",
    "webhook": false,
    "terminal": false,
    "act": "None; the applicant may come back to it."
  },
  "type": "object",
  "required": [
    "application_id",
    "missing"
  ],
  "properties": {
    "application_id": {
      "type": "string"
    },
    "missing": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Fields still missing for submission."
    }
  },
  "additionalProperties": false
}

psp.application.submitted

A PSP application was submitted and a PSP record created in state applied. v2: the application carries the partner kind (psp or lsp) and an LSP its biller category. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.application.submitted/v2",
  "title": "psp.application.submitted",
  "description": "A PSP application was submitted and a PSP record created in state applied. v2: the application carries the partner kind (psp or lsp) and an LSP its biller category.",
  "x-kulpay": {
    "aggregate": "psp_application",
    "webhook": false,
    "terminal": false,
    "act": "Start the KYB review."
  },
  "type": "object",
  "required": [
    "application_id",
    "psp_id",
    "account_id",
    "region",
    "application"
  ],
  "properties": {
    "application_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "region": {
      "type": "string"
    },
    "application": {
      "$ref": "kulpay://objects/psp_application.v2.json"
    }
  },
  "additionalProperties": false
}

psp_event events

EventWebhookTerminalFires whenWhat to do
psp_event.dead_lettered v1 no no An event a PSP sent could not be processed (IO-0.5): not an event, a sequence already applied, no handler for its type, or data its handler refuses. Its aggregate waits behind it until an operator replays it. An operator reads the reason, has the PSP fix it, and replays the event.
psp_event.received v1 no no An event a PSP notified KulPay of (IO-0.5), verified under its binding: applied in its aggregate's order, or held until the events before it arrive (KulPay reads the missing ones from the PSP). None.

psp_event.dead_lettered

An event a PSP sent could not be processed (IO-0.5): not an event, a sequence already applied, no handler for its type, or data its handler refuses. Its aggregate waits behind it until an operator replays it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp_event.dead_lettered/v1",
  "title": "psp_event.dead_lettered",
  "description": "An event a PSP sent could not be processed (IO-0.5): not an event, a sequence already applied, no handler for its type, or data its handler refuses. Its aggregate waits behind it until an operator replays it.",
  "x-kulpay": {
    "aggregate": "psp_event",
    "webhook": false,
    "terminal": false,
    "act": "An operator reads the reason, has the PSP fix it, and replays the event."
  },
  "type": "object",
  "required": [
    "psp_event_id",
    "event_id",
    "type",
    "binding_id",
    "aggregate_type",
    "aggregate_id",
    "sequence",
    "status",
    "reason"
  ],
  "properties": {
    "psp_event_id": {
      "type": "string"
    },
    "event_id": {
      "type": "string",
      "description": "The PSP's event ID."
    },
    "type": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "binding_id": {
      "type": "string"
    },
    "aggregate_type": {
      "type": "string"
    },
    "aggregate_id": {
      "type": "string"
    },
    "sequence": {
      "type": "integer",
      "minimum": 0
    },
    "status": {
      "enum": [
        "applied",
        "held",
        "dead"
      ]
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp_event.received

An event a PSP notified KulPay of (IO-0.5), verified under its binding: applied in its aggregate's order, or held until the events before it arrive (KulPay reads the missing ones from the PSP). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp_event.received/v1",
  "title": "psp_event.received",
  "description": "An event a PSP notified KulPay of (IO-0.5), verified under its binding: applied in its aggregate's order, or held until the events before it arrive (KulPay reads the missing ones from the PSP).",
  "x-kulpay": {
    "aggregate": "psp_event",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_event_id",
    "event_id",
    "type",
    "binding_id",
    "aggregate_type",
    "aggregate_id",
    "sequence",
    "status"
  ],
  "properties": {
    "psp_event_id": {
      "type": "string"
    },
    "event_id": {
      "type": "string",
      "description": "The PSP's event ID."
    },
    "type": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "binding_id": {
      "type": "string"
    },
    "aggregate_type": {
      "type": "string"
    },
    "aggregate_id": {
      "type": "string"
    },
    "sequence": {
      "type": "integer",
      "minimum": 0
    },
    "status": {
      "enum": [
        "applied",
        "held",
        "dead"
      ]
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp_portal_user events

EventWebhookTerminalFires whenWhat to do
psp_portal.session.ended v1 no no A PSP portal session ended before it expired (PSP-13.1): the user signed out or was removed. None.
psp_portal.session.started v1 no no A portal user signed in to the PSP portal with an emailed link (PSP-13.1); the session lasts 8 hours. None.
psp_portal.sign_in_link.sent v1 no no A sign-in link to the PSP portal was emailed to a portal user (PSP-13.1): single use, valid for 15 minutes. The link itself is never recorded. None. Many links for one user in a short time may mean someone is trying their email.
psp_portal.user.invited v1 no no A person became a user of a PSP's portal (PSP-13.1, PSP-13.4): an application contact on submission, or someone a PSP admin invited. None.
psp_portal.user.removed v1 no no A PSP admin removed a portal user (PSP-13.4); the user's sessions ended at once. None.
psp_portal.user.role_changed v1 no no A PSP admin changed a portal user's role (PSP-13.4). A PSP always keeps at least one admin. None.

psp_portal.session.ended

A PSP portal session ended before it expired (PSP-13.1): the user signed out or was removed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp_portal.session.ended/v1",
  "title": "psp_portal.session.ended",
  "description": "A PSP portal session ended before it expired (PSP-13.1): the user signed out or was removed.",
  "x-kulpay": {
    "aggregate": "psp_portal_user",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "user_id",
    "session_id",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "user_id": {
      "type": "string"
    },
    "session_id": {
      "type": "string"
    },
    "reason": {
      "enum": [
        "signed_out",
        "user_removed"
      ]
    }
  },
  "additionalProperties": false
}

psp_portal.session.started

A portal user signed in to the PSP portal with an emailed link (PSP-13.1); the session lasts 8 hours. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp_portal.session.started/v1",
  "title": "psp_portal.session.started",
  "description": "A portal user signed in to the PSP portal with an emailed link (PSP-13.1); the session lasts 8 hours.",
  "x-kulpay": {
    "aggregate": "psp_portal_user",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "user_id",
    "session_id",
    "expires_at"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "user_id": {
      "type": "string"
    },
    "session_id": {
      "type": "string"
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

psp_portal.sign_in_link.sent

A sign-in link to the PSP portal was emailed to a portal user (PSP-13.1): single use, valid for 15 minutes. The link itself is never recorded. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp_portal.sign_in_link.sent/v1",
  "title": "psp_portal.sign_in_link.sent",
  "description": "A sign-in link to the PSP portal was emailed to a portal user (PSP-13.1): single use, valid for 15 minutes. The link itself is never recorded.",
  "x-kulpay": {
    "aggregate": "psp_portal_user",
    "webhook": false,
    "terminal": false,
    "act": "None. Many links for one user in a short time may mean someone is trying their email."
  },
  "type": "object",
  "required": [
    "psp_id",
    "user_id",
    "expires_at"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "user_id": {
      "type": "string"
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

psp_portal.user.invited

A person became a user of a PSP's portal (PSP-13.1, PSP-13.4): an application contact on submission, or someone a PSP admin invited. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp_portal.user.invited/v1",
  "title": "psp_portal.user.invited",
  "description": "A person became a user of a PSP's portal (PSP-13.1, PSP-13.4): an application contact on submission, or someone a PSP admin invited.",
  "x-kulpay": {
    "aggregate": "psp_portal_user",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "user_id",
    "role",
    "invited_by"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "user_id": {
      "type": "string"
    },
    "role": {
      "enum": [
        "admin",
        "developer",
        "finance",
        "viewer"
      ]
    },
    "invited_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp_portal.user.removed

A PSP admin removed a portal user (PSP-13.4); the user's sessions ended at once. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp_portal.user.removed/v1",
  "title": "psp_portal.user.removed",
  "description": "A PSP admin removed a portal user (PSP-13.4); the user's sessions ended at once.",
  "x-kulpay": {
    "aggregate": "psp_portal_user",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "user_id",
    "removed_by"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "user_id": {
      "type": "string"
    },
    "removed_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

psp_portal.user.role_changed

A PSP admin changed a portal user's role (PSP-13.4). A PSP always keeps at least one admin. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp_portal.user.role_changed/v1",
  "title": "psp_portal.user.role_changed",
  "description": "A PSP admin changed a portal user's role (PSP-13.4). A PSP always keeps at least one admin.",
  "x-kulpay": {
    "aggregate": "psp_portal_user",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "user_id",
    "from",
    "to",
    "changed_by"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "user_id": {
      "type": "string"
    },
    "from": {
      "enum": [
        "admin",
        "developer",
        "finance",
        "viewer"
      ]
    },
    "to": {
      "enum": [
        "admin",
        "developer",
        "finance",
        "viewer"
      ]
    },
    "changed_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

settlement_report events

EventWebhookTerminalFires whenWhat to do
recon.cash.matched v1 no no A settlement report's net was found on a bank statement as a credit on its expected value date (PSP-11.8); a late_settlement it had raised is closed. None.
recon.settlement.matched v1 no no A settlement report was compared with what KulPay expected (PSP-11.8): the day's captured payments through the PSP and its contracted fees, or, in the direct model, the settlement batch. status is matched when gross and fees agree, variance otherwise (fee_variance, settlement_shortfall or amount_mismatch is raised). On variance, work the exception; fee variances export as a claim file.

recon.cash.matched

A settlement report's net was found on a bank statement as a credit on its expected value date (PSP-11.8); a late_settlement it had raised is closed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.cash.matched/v1",
  "title": "recon.cash.matched",
  "description": "A settlement report's net was found on a bank statement as a credit on its expected value date (PSP-11.8); a late_settlement it had raised is closed.",
  "x-kulpay": {
    "aggregate": "settlement_report",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "report_id",
    "settlement_id",
    "psp_id",
    "date",
    "currency",
    "gross_minor",
    "fees_minor",
    "net_minor",
    "expected_gross_minor",
    "expected_fees_minor",
    "status",
    "cash_status",
    "line_key",
    "statement_file_id"
  ],
  "properties": {
    "report_id": {
      "type": "string"
    },
    "settlement_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "date": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "gross_minor": {
      "type": "integer"
    },
    "fees_minor": {
      "type": "integer"
    },
    "net_minor": {
      "type": "integer"
    },
    "expected_gross_minor": {
      "type": "integer"
    },
    "expected_fees_minor": {
      "type": "integer"
    },
    "status": {
      "type": "string",
      "enum": [
        "matched",
        "variance"
      ]
    },
    "cash_status": {
      "type": "string",
      "enum": [
        "pending",
        "matched",
        "late"
      ]
    },
    "batch_id": {
      "type": "string"
    },
    "line_key": {
      "type": "string"
    },
    "statement_file_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.settlement.matched

A settlement report was compared with what KulPay expected (PSP-11.8): the day's captured payments through the PSP and its contracted fees, or, in the direct model, the settlement batch. status is matched when gross and fees agree, variance otherwise (fee_variance, settlement_shortfall or amount_mismatch is raised). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.settlement.matched/v1",
  "title": "recon.settlement.matched",
  "description": "A settlement report was compared with what KulPay expected (PSP-11.8): the day's captured payments through the PSP and its contracted fees, or, in the direct model, the settlement batch. status is matched when gross and fees agree, variance otherwise (fee_variance, settlement_shortfall or amount_mismatch is raised).",
  "x-kulpay": {
    "aggregate": "settlement_report",
    "webhook": false,
    "terminal": false,
    "act": "On variance, work the exception; fee variances export as a claim file."
  },
  "type": "object",
  "required": [
    "report_id",
    "settlement_id",
    "psp_id",
    "date",
    "currency",
    "gross_minor",
    "fees_minor",
    "net_minor",
    "expected_gross_minor",
    "expected_fees_minor",
    "status",
    "cash_status"
  ],
  "properties": {
    "report_id": {
      "type": "string"
    },
    "settlement_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "date": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "gross_minor": {
      "type": "integer"
    },
    "fees_minor": {
      "type": "integer"
    },
    "net_minor": {
      "type": "integer"
    },
    "expected_gross_minor": {
      "type": "integer"
    },
    "expected_fees_minor": {
      "type": "integer"
    },
    "status": {
      "type": "string",
      "enum": [
        "matched",
        "variance"
      ]
    },
    "cash_status": {
      "type": "string",
      "enum": [
        "pending",
        "matched",
        "late"
      ]
    },
    "batch_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon_exception events

EventWebhookTerminalFires whenWhat to do
recon.exception.assigned v1 no no A reconciliation exception was given an owner and is being investigated (PSP-11.7). The owner works it before the due date.
recon.exception.claimed v1 no no A claim was raised with the PSP about a reconciliation exception (PSP-11.7); the exception stays under investigation until the PSP answers. Follow the claim with the PSP.
recon.exception.commented v1 no no A comment was added to a reconciliation exception (PSP-11.7); comments are never edited. None.
recon.exception.compensated v1 no no A compensating event was recorded for a reconciliation exception (PSP-11.7): what was done elsewhere to correct it; the records it is about are not edited. None.
recon.exception.escalated v1 no no A reconciliation exception passed its due date while still open and was escalated to the finance lead, once (PSP-11.7). The finance lead follows it up.
recon.exception.raised v1 no no Reconciliation raised an exception (PSP-11.6, PSP-11.7): an item it cannot settle on its own, such as ambiguous_match, missing_at_psp or fee_variance, due in 2 working days by default. Assign it and work it in the exception queue.
recon.exception.resolved v1 no yes A reconciliation exception was resolved or written off (PSP-11.7), with the resolution recorded; the records it is about are never edited. None.

recon.exception.assigned

A reconciliation exception was given an owner and is being investigated (PSP-11.7). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.exception.assigned/v1",
  "title": "recon.exception.assigned",
  "description": "A reconciliation exception was given an owner and is being investigated (PSP-11.7).",
  "x-kulpay": {
    "aggregate": "recon_exception",
    "webhook": false,
    "terminal": false,
    "act": "The owner works it before the due date."
  },
  "type": "object",
  "required": [
    "exception_id",
    "type",
    "status",
    "severity",
    "due_at",
    "assigned_by"
  ],
  "properties": {
    "exception_id": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "status": {
      "type": "string"
    },
    "severity": {
      "type": "string"
    },
    "due_at": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "line_key": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "owner": {
      "type": "string"
    },
    "detail": {
      "type": "string"
    },
    "assigned_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.exception.claimed

A claim was raised with the PSP about a reconciliation exception (PSP-11.7); the exception stays under investigation until the PSP answers. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.exception.claimed/v1",
  "title": "recon.exception.claimed",
  "description": "A claim was raised with the PSP about a reconciliation exception (PSP-11.7); the exception stays under investigation until the PSP answers.",
  "x-kulpay": {
    "aggregate": "recon_exception",
    "webhook": false,
    "terminal": false,
    "act": "Follow the claim with the PSP."
  },
  "type": "object",
  "required": [
    "exception_id",
    "type",
    "status",
    "severity",
    "due_at",
    "claim",
    "by"
  ],
  "properties": {
    "exception_id": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "status": {
      "type": "string"
    },
    "severity": {
      "type": "string"
    },
    "due_at": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "line_key": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "owner": {
      "type": "string"
    },
    "detail": {
      "type": "string"
    },
    "claim": {
      "type": "string"
    },
    "by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.exception.commented

A comment was added to a reconciliation exception (PSP-11.7); comments are never edited. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.exception.commented/v1",
  "title": "recon.exception.commented",
  "description": "A comment was added to a reconciliation exception (PSP-11.7); comments are never edited.",
  "x-kulpay": {
    "aggregate": "recon_exception",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "exception_id",
    "type",
    "status",
    "severity",
    "due_at",
    "comment_id",
    "by"
  ],
  "properties": {
    "exception_id": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "status": {
      "type": "string"
    },
    "severity": {
      "type": "string"
    },
    "due_at": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "line_key": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "owner": {
      "type": "string"
    },
    "detail": {
      "type": "string"
    },
    "comment_id": {
      "type": "string"
    },
    "by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.exception.compensated

A compensating event was recorded for a reconciliation exception (PSP-11.7): what was done elsewhere to correct it; the records it is about are not edited. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.exception.compensated/v1",
  "title": "recon.exception.compensated",
  "description": "A compensating event was recorded for a reconciliation exception (PSP-11.7): what was done elsewhere to correct it; the records it is about are not edited.",
  "x-kulpay": {
    "aggregate": "recon_exception",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "exception_id",
    "type",
    "status",
    "severity",
    "due_at",
    "compensation"
  ],
  "properties": {
    "exception_id": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "status": {
      "type": "string"
    },
    "severity": {
      "type": "string"
    },
    "due_at": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "line_key": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "owner": {
      "type": "string"
    },
    "detail": {
      "type": "string"
    },
    "compensation": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.exception.escalated

A reconciliation exception passed its due date while still open and was escalated to the finance lead, once (PSP-11.7). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.exception.escalated/v1",
  "title": "recon.exception.escalated",
  "description": "A reconciliation exception passed its due date while still open and was escalated to the finance lead, once (PSP-11.7).",
  "x-kulpay": {
    "aggregate": "recon_exception",
    "webhook": false,
    "terminal": false,
    "act": "The finance lead follows it up."
  },
  "type": "object",
  "required": [
    "exception_id",
    "type",
    "status",
    "severity",
    "due_at",
    "overdue_since"
  ],
  "properties": {
    "exception_id": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "status": {
      "type": "string"
    },
    "severity": {
      "type": "string"
    },
    "due_at": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "line_key": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "owner": {
      "type": "string"
    },
    "detail": {
      "type": "string"
    },
    "overdue_since": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.exception.raised

Reconciliation raised an exception (PSP-11.6, PSP-11.7): an item it cannot settle on its own, such as ambiguous_match, missing_at_psp or fee_variance, due in 2 working days by default. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.exception.raised/v1",
  "title": "recon.exception.raised",
  "description": "Reconciliation raised an exception (PSP-11.6, PSP-11.7): an item it cannot settle on its own, such as ambiguous_match, missing_at_psp or fee_variance, due in 2 working days by default.",
  "x-kulpay": {
    "aggregate": "recon_exception",
    "webhook": false,
    "terminal": false,
    "act": "Assign it and work it in the exception queue."
  },
  "type": "object",
  "required": [
    "exception_id",
    "type",
    "status",
    "severity",
    "due_at"
  ],
  "properties": {
    "exception_id": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "status": {
      "type": "string"
    },
    "severity": {
      "type": "string"
    },
    "due_at": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "line_key": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "owner": {
      "type": "string"
    },
    "detail": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.exception.resolved

A reconciliation exception was resolved or written off (PSP-11.7), with the resolution recorded; the records it is about are never edited. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.exception.resolved/v1",
  "title": "recon.exception.resolved",
  "description": "A reconciliation exception was resolved or written off (PSP-11.7), with the resolution recorded; the records it is about are never edited.",
  "x-kulpay": {
    "aggregate": "recon_exception",
    "webhook": false,
    "terminal": true,
    "act": "None."
  },
  "type": "object",
  "required": [
    "exception_id",
    "type",
    "status",
    "severity",
    "due_at",
    "resolution"
  ],
  "properties": {
    "resolution": {
      "type": "string"
    },
    "exception_id": {
      "type": "string"
    },
    "type": {
      "type": "string"
    },
    "status": {
      "type": "string"
    },
    "severity": {
      "type": "string"
    },
    "due_at": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "line_key": {
      "type": "string"
    },
    "subject_type": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "owner": {
      "type": "string"
    },
    "detail": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

statement_file events

EventWebhookTerminalFires whenWhat to do
recon.file.duplicate v1 no yes A statement file was received before (same SHA-256, or the same sequence number for the account) and was not imported again (PSP-11.2). duplicate_of is the earlier file. None, unless the PSP meant to send a corrected file: it needs a new sequence number.
recon.file.fetched v1 no no KulPay fetched a statement from the PSP's SFTP server or REST API under a pull profile (PSP-11.3), with credentials from the vault, and ingested it like a pushed file. None.
recon.file.ingested v1 no no A statement file was stored unchanged in the region's object storage with its checksum and profile version, and parsed into canonical lines linked to it (PSP-11.5). Lines whose ID was imported for the account from another file are left out (skipped_lines). Matching can use the lines.
recon.file.missing v1 no yes A statement expected under a profile had not arrived by the end of its arrival window after the cut-off (PSP-11.3); for pull profiles, after fetching every 30 minutes for up to 4 hours. The PSP's contacts are emailed. Chase the PSP; reconciliation for the day waits for this statement.
recon.file.received v1 no no A statement file arrived under one of the PSP's statement profiles, through any channel (PSP-11.2): recorded before any check, with its SHA-256, so every arrival is traceable. None: an ingested, rejected or duplicate event follows.
recon.file.rejected v1 no yes A statement file was refused (PSP-11.2): checksum mismatch or missing, PGP decryption or signature failure, a parse error, balances that do not add up, or an account the profile does not cover. The original is kept for investigation. Tell the PSP the reason; a corrected file can be sent again.
recon.file.reparsed v1 no no A stored statement was parsed again with the profile now in force, after a mapping fix (PSP-11.5): a new parse version; the earlier ones are kept. Matching uses the new parse version.
recon.match.auto v1 no no A statement's lines were matched to transactions by the ordered rules (PSP-11.6): exact, kulpay_ref, fuzzy, many_to_one, tolerance. Each match keeps its rule, confidence, lines and transactions; re-running matches nothing twice. Work the ambiguous and unmatched items in the exception queue.

recon.file.duplicate

A statement file was received before (same SHA-256, or the same sequence number for the account) and was not imported again (PSP-11.2). duplicate_of is the earlier file. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.file.duplicate/v1",
  "title": "recon.file.duplicate",
  "description": "A statement file was received before (same SHA-256, or the same sequence number for the account) and was not imported again (PSP-11.2). duplicate_of is the earlier file.",
  "x-kulpay": {
    "aggregate": "statement_file",
    "webhook": false,
    "terminal": true,
    "act": "None, unless the PSP meant to send a corrected file: it needs a new sequence number."
  },
  "type": "object",
  "required": [
    "psp_id",
    "profile_id",
    "sha256",
    "duplicate_of",
    "reason"
  ],
  "properties": {
    "duplicate_of": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "profile_id": {
      "type": "string"
    },
    "profile_version": {
      "type": "integer"
    },
    "channel": {
      "type": "string"
    },
    "filename": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "size": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

recon.file.fetched

KulPay fetched a statement from the PSP's SFTP server or REST API under a pull profile (PSP-11.3), with credentials from the vault, and ingested it like a pushed file. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.file.fetched/v1",
  "title": "recon.file.fetched",
  "description": "KulPay fetched a statement from the PSP's SFTP server or REST API under a pull profile (PSP-11.3), with credentials from the vault, and ingested it like a pushed file.",
  "x-kulpay": {
    "aggregate": "statement_file",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "profile_id",
    "channel",
    "sha256"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "profile_id": {
      "type": "string"
    },
    "channel": {
      "type": "string"
    },
    "filename": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.file.ingested

A statement file was stored unchanged in the region's object storage with its checksum and profile version, and parsed into canonical lines linked to it (PSP-11.5). Lines whose ID was imported for the account from another file are left out (skipped_lines). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.file.ingested/v1",
  "title": "recon.file.ingested",
  "description": "A statement file was stored unchanged in the region's object storage with its checksum and profile version, and parsed into canonical lines linked to it (PSP-11.5). Lines whose ID was imported for the account from another file are left out (skipped_lines).",
  "x-kulpay": {
    "aggregate": "statement_file",
    "webhook": false,
    "terminal": false,
    "act": "Matching can use the lines."
  },
  "type": "object",
  "required": [
    "psp_id",
    "profile_id",
    "profile_version",
    "file_id",
    "sha256",
    "parse_version",
    "line_count",
    "skipped_lines"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "profile_id": {
      "type": "string"
    },
    "profile_version": {
      "type": "integer"
    },
    "file_id": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "parse_version": {
      "type": "integer"
    },
    "line_count": {
      "type": "integer"
    },
    "skipped_lines": {
      "type": "integer"
    },
    "statement_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "balance_checked": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

recon.file.missing

A statement expected under a profile had not arrived by the end of its arrival window after the cut-off (PSP-11.3); for pull profiles, after fetching every 30 minutes for up to 4 hours. The PSP's contacts are emailed. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.file.missing/v1",
  "title": "recon.file.missing",
  "description": "A statement expected under a profile had not arrived by the end of its arrival window after the cut-off (PSP-11.3); for pull profiles, after fetching every 30 minutes for up to 4 hours. The PSP's contacts are emailed.",
  "x-kulpay": {
    "aggregate": "statement_file",
    "webhook": false,
    "terminal": true,
    "act": "Chase the PSP; reconciliation for the day waits for this statement."
  },
  "type": "object",
  "required": [
    "psp_id",
    "profile_id",
    "cycle",
    "cutoff",
    "arrival_window",
    "attempts"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "profile_id": {
      "type": "string"
    },
    "cycle": {
      "type": "string"
    },
    "cutoff": {
      "type": "string"
    },
    "arrival_window": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    },
    "last_error": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.file.received

A statement file arrived under one of the PSP's statement profiles, through any channel (PSP-11.2): recorded before any check, with its SHA-256, so every arrival is traceable. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.file.received/v1",
  "title": "recon.file.received",
  "description": "A statement file arrived under one of the PSP's statement profiles, through any channel (PSP-11.2): recorded before any check, with its SHA-256, so every arrival is traceable.",
  "x-kulpay": {
    "aggregate": "statement_file",
    "webhook": false,
    "terminal": false,
    "act": "None: an ingested, rejected or duplicate event follows."
  },
  "type": "object",
  "required": [
    "psp_id",
    "profile_id",
    "profile_version",
    "channel",
    "sha256",
    "size"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "profile_id": {
      "type": "string"
    },
    "profile_version": {
      "type": "integer"
    },
    "channel": {
      "type": "string"
    },
    "filename": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "size": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

recon.file.rejected

A statement file was refused (PSP-11.2): checksum mismatch or missing, PGP decryption or signature failure, a parse error, balances that do not add up, or an account the profile does not cover. The original is kept for investigation. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.file.rejected/v1",
  "title": "recon.file.rejected",
  "description": "A statement file was refused (PSP-11.2): checksum mismatch or missing, PGP decryption or signature failure, a parse error, balances that do not add up, or an account the profile does not cover. The original is kept for investigation.",
  "x-kulpay": {
    "aggregate": "statement_file",
    "webhook": false,
    "terminal": true,
    "act": "Tell the PSP the reason; a corrected file can be sent again."
  },
  "type": "object",
  "required": [
    "psp_id",
    "profile_id",
    "profile_version",
    "sha256",
    "file_id",
    "reason"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "profile_id": {
      "type": "string"
    },
    "profile_version": {
      "type": "integer"
    },
    "sha256": {
      "type": "string"
    },
    "file_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon.file.reparsed

A stored statement was parsed again with the profile now in force, after a mapping fix (PSP-11.5): a new parse version; the earlier ones are kept. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.file.reparsed/v1",
  "title": "recon.file.reparsed",
  "description": "A stored statement was parsed again with the profile now in force, after a mapping fix (PSP-11.5): a new parse version; the earlier ones are kept.",
  "x-kulpay": {
    "aggregate": "statement_file",
    "webhook": false,
    "terminal": false,
    "act": "Matching uses the new parse version."
  },
  "type": "object",
  "required": [
    "psp_id",
    "profile_id",
    "profile_version",
    "file_id",
    "sha256",
    "parse_version",
    "line_count",
    "skipped_lines",
    "reason"
  ],
  "properties": {
    "reason": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "profile_id": {
      "type": "string"
    },
    "profile_version": {
      "type": "integer"
    },
    "file_id": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "parse_version": {
      "type": "integer"
    },
    "line_count": {
      "type": "integer"
    },
    "skipped_lines": {
      "type": "integer"
    },
    "statement_id": {
      "type": "string"
    },
    "account": {
      "type": "string"
    },
    "balance_checked": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

recon.match.auto

A statement's lines were matched to transactions by the ordered rules (PSP-11.6): exact, kulpay_ref, fuzzy, many_to_one, tolerance. Each match keeps its rule, confidence, lines and transactions; re-running matches nothing twice. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.match.auto/v1",
  "title": "recon.match.auto",
  "description": "A statement's lines were matched to transactions by the ordered rules (PSP-11.6): exact, kulpay_ref, fuzzy, many_to_one, tolerance. Each match keeps its rule, confidence, lines and transactions; re-running matches nothing twice.",
  "x-kulpay": {
    "aggregate": "statement_file",
    "webhook": false,
    "terminal": false,
    "act": "Work the ambiguous and unmatched items in the exception queue."
  },
  "type": "object",
  "required": [
    "statement_file_id",
    "psp_id",
    "lines",
    "matched",
    "by_rule",
    "ambiguous",
    "unmatched",
    "already_matched"
  ],
  "properties": {
    "statement_file_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "lines": {
      "type": "integer"
    },
    "matched": {
      "type": "integer"
    },
    "by_rule": {
      "type": "object",
      "additionalProperties": {
        "type": "integer"
      }
    },
    "ambiguous": {
      "type": "integer"
    },
    "unmatched": {
      "type": "integer"
    },
    "already_matched": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

recon_match events

EventWebhookTerminalFires whenWhat to do
recon.match.manual v1 no yes A person matched statement lines to transactions by hand, with a reason; the amounts add up (PSP-11.6). None.

recon.match.manual

A person matched statement lines to transactions by hand, with a reason; the amounts add up (PSP-11.6). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.match.manual/v1",
  "title": "recon.match.manual",
  "description": "A person matched statement lines to transactions by hand, with a reason; the amounts add up (PSP-11.6).",
  "x-kulpay": {
    "aggregate": "recon_match",
    "webhook": false,
    "terminal": true,
    "act": "None."
  },
  "type": "object",
  "required": [
    "match_id",
    "line_keys",
    "transactions",
    "reason",
    "actor"
  ],
  "properties": {
    "match_id": {
      "type": "string"
    },
    "line_keys": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "transactions": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "reason": {
      "type": "string"
    },
    "actor": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

recon_period events

EventWebhookTerminalFires whenWhat to do
recon.period.closed v1 no yes A month was closed (PSP-11.9): the checklist passed (statements received, ledger balanced, safeguarding proven every business day, exceptions resolved or carried forward with reasons), one person prepared it and another signed it off. The signed report is kept write-once and the period is locked: later postings dated in it land in the next open period as adjustments. None: the month is closed.

recon.period.closed

A month was closed (PSP-11.9): the checklist passed (statements received, ledger balanced, safeguarding proven every business day, exceptions resolved or carried forward with reasons), one person prepared it and another signed it off. The signed report is kept write-once and the period is locked: later postings dated in it land in the next open period as adjustments. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/recon.period.closed/v1",
  "title": "recon.period.closed",
  "description": "A month was closed (PSP-11.9): the checklist passed (statements received, ledger balanced, safeguarding proven every business day, exceptions resolved or carried forward with reasons), one person prepared it and another signed it off. The signed report is kept write-once and the period is locked: later postings dated in it land in the next open period as adjustments.",
  "x-kulpay": {
    "aggregate": "recon_period",
    "webhook": false,
    "terminal": true,
    "act": "None: the month is closed."
  },
  "type": "object",
  "required": [
    "period",
    "region",
    "prepared_by",
    "approved_by",
    "carried_forward"
  ],
  "properties": {
    "period": {
      "type": "string"
    },
    "region": {
      "type": "string"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    },
    "carried_forward": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

reconciliation_statement events

EventWebhookTerminalFires whenWhat to do
reconciliation.exception.resolved v1 no no An operator resolved a reconciliation exception. Audit record. Compare before and after.
reconciliation.run.completed v1 no no A matching pass over a statement finished. Audit record.
reconciliation.statement.closed v1 no no A statement was closed after every exception was resolved. Audit record. Compare before and after.
reconciliation.statement.imported v1 no no A provider statement was imported. Audit record. Compare before and after.

reconciliation.exception.resolved

An operator resolved a reconciliation exception. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reconciliation.exception.resolved/v1",
  "title": "reconciliation.exception.resolved",
  "description": "An operator resolved a reconciliation exception.",
  "x-kulpay": {
    "aggregate": "reconciliation_statement",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "before",
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/statement_line.json"
    },
    "after": {
      "$ref": "kulpay://objects/statement_line.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

reconciliation.run.completed

A matching pass over a statement finished. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reconciliation.run.completed/v1",
  "title": "reconciliation.run.completed",
  "description": "A matching pass over a statement finished.",
  "x-kulpay": {
    "aggregate": "reconciliation_statement",
    "webhook": false,
    "terminal": false,
    "act": "Audit record."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "after": {
      "$ref": "kulpay://objects/reconciliation_run.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

reconciliation.statement.closed

A statement was closed after every exception was resolved. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reconciliation.statement.closed/v1",
  "title": "reconciliation.statement.closed",
  "description": "A statement was closed after every exception was resolved.",
  "x-kulpay": {
    "aggregate": "reconciliation_statement",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/reconciliation_statement.json"
    },
    "after": {
      "$ref": "kulpay://objects/reconciliation_statement.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

reconciliation.statement.imported

A provider statement was imported. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reconciliation.statement.imported/v1",
  "title": "reconciliation.statement.imported",
  "description": "A provider statement was imported.",
  "x-kulpay": {
    "aggregate": "reconciliation_statement",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/reconciliation_statement.json"
    },
    "after": {
      "$ref": "kulpay://objects/reconciliation_statement.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

refund events

EventWebhookTerminalFires whenWhat to do
refund.created v1 no no A refund was reserved against the payment balance and sent to the provider. Progress only.
refund.failed v1 yes yes A refund could not be applied. The original payment stands. Investigate before retrying.
refund.released v1 no no The provider did not complete the refund, so the reserved amount was released back to the refundable balance. The payment can be refunded again.
refund.succeeded v1 yes yes A refund settled. Reverse the fulfilment. Check amount: refunds can be partial.
reversal.failed v1 yes yes The provider did not reverse the transaction, or an approver rejected the reversal; any reserved amount is released. Check the reason; refund instead if the provider cannot reverse.
reversal.requested v1 no no A reversal was requested: through the API it runs at once; a manual one waits for a second person. A settled payment's amount is reserved. Approve or reject it if it waits for approval.
reversal.succeeded v1 yes yes The provider reversed the transaction; the ledger posts a reversing entry and the payment is refunded, partially refunded, or (if its outcome was uncertain) cancelled. None.

refund.created

A refund was reserved against the payment balance and sent to the provider. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/refund.created/v1",
  "title": "refund.created",
  "description": "A refund was reserved against the payment balance and sent to the provider.",
  "x-kulpay": {
    "aggregate": "refund",
    "webhook": false,
    "terminal": false,
    "act": "Progress only."
  },
  "$ref": "kulpay://objects/refund.json"
}

refund.failed

A refund could not be applied. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/refund.failed/v1",
  "title": "refund.failed",
  "description": "A refund could not be applied.",
  "x-kulpay": {
    "aggregate": "refund",
    "webhook": true,
    "terminal": true,
    "act": "The original payment stands. Investigate before retrying."
  },
  "$ref": "kulpay://objects/refund.json"
}

refund.released

The provider did not complete the refund, so the reserved amount was released back to the refundable balance. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/refund.released/v1",
  "title": "refund.released",
  "description": "The provider did not complete the refund, so the reserved amount was released back to the refundable balance.",
  "x-kulpay": {
    "aggregate": "refund",
    "webhook": false,
    "terminal": false,
    "act": "The payment can be refunded again."
  },
  "$ref": "kulpay://objects/refund.json"
}

refund.succeeded

A refund settled. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/refund.succeeded/v1",
  "title": "refund.succeeded",
  "description": "A refund settled.",
  "x-kulpay": {
    "aggregate": "refund",
    "webhook": true,
    "terminal": true,
    "act": "Reverse the fulfilment. Check amount: refunds can be partial."
  },
  "$ref": "kulpay://objects/refund.json"
}

reversal.failed

The provider did not reverse the transaction, or an approver rejected the reversal; any reserved amount is released. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reversal.failed/v1",
  "title": "reversal.failed",
  "description": "The provider did not reverse the transaction, or an approver rejected the reversal; any reserved amount is released.",
  "x-kulpay": {
    "aggregate": "refund",
    "webhook": true,
    "terminal": true,
    "act": "Check the reason; refund instead if the provider cannot reverse."
  },
  "$ref": "kulpay://objects/reversal.json"
}

reversal.requested

A reversal was requested: through the API it runs at once; a manual one waits for a second person. A settled payment's amount is reserved. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reversal.requested/v1",
  "title": "reversal.requested",
  "description": "A reversal was requested: through the API it runs at once; a manual one waits for a second person. A settled payment's amount is reserved.",
  "x-kulpay": {
    "aggregate": "refund",
    "webhook": false,
    "terminal": false,
    "act": "Approve or reject it if it waits for approval."
  },
  "$ref": "kulpay://objects/reversal.json"
}

reversal.succeeded

The provider reversed the transaction; the ledger posts a reversing entry and the payment is refunded, partially refunded, or (if its outcome was uncertain) cancelled. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/reversal.succeeded/v1",
  "title": "reversal.succeeded",
  "description": "The provider reversed the transaction; the ledger posts a reversing entry and the payment is refunded, partially refunded, or (if its outcome was uncertain) cancelled.",
  "x-kulpay": {
    "aggregate": "refund",
    "webhook": true,
    "terminal": true,
    "act": "None."
  },
  "$ref": "kulpay://objects/reversal.json"
}

regulatory_run events

EventWebhookTerminalFires whenWhat to do
regulatory_report.generated v1 no no A regulatory report was generated from a template (PSP-10.6), daily, monthly or on demand: signed and stored write-once. reconciled tells whether its totals equal the ledger's for the same period; a blocked report cannot be submitted. If blocked, find the difference before submitting.
regulatory_report.receipt_attached v1 no no The regulator's receipt of a submitted report was kept, write-once, beside the signed file (F3-7). None.
regulatory_report.submitted v2 no no A reconciled regulatory report was submitted to the regulator once a second person approved (F3-7): submitted_by prepared it, approved_by approved it, by channel with the reference the submission got; the report is kept until retain_until. None.

regulatory_report.generated

A regulatory report was generated from a template (PSP-10.6), daily, monthly or on demand: signed and stored write-once. reconciled tells whether its totals equal the ledger's for the same period; a blocked report cannot be submitted. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/regulatory_report.generated/v1",
  "title": "regulatory_report.generated",
  "description": "A regulatory report was generated from a template (PSP-10.6), daily, monthly or on demand: signed and stored write-once. reconciled tells whether its totals equal the ledger's for the same period; a blocked report cannot be submitted.",
  "x-kulpay": {
    "aggregate": "regulatory_run",
    "webhook": false,
    "terminal": false,
    "act": "If blocked, find the difference before submitting."
  },
  "type": "object",
  "required": [
    "run_id",
    "template_id",
    "code",
    "template_version",
    "period_start",
    "period_end",
    "format",
    "rows",
    "reconciled",
    "status",
    "sha256",
    "trigger"
  ],
  "properties": {
    "run_id": {
      "type": "string"
    },
    "template_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "template_version": {
      "type": "integer"
    },
    "period_start": {
      "type": "string"
    },
    "period_end": {
      "type": "string"
    },
    "format": {
      "type": "string"
    },
    "rows": {
      "type": "integer"
    },
    "reconciled": {
      "type": "boolean"
    },
    "status": {
      "type": "string",
      "enum": [
        "generated",
        "blocked"
      ]
    },
    "sha256": {
      "type": "string"
    },
    "trigger": {
      "type": "string",
      "enum": [
        "scheduled",
        "on_demand"
      ]
    }
  },
  "additionalProperties": false
}

regulatory_report.receipt_attached

The regulator's receipt of a submitted report was kept, write-once, beside the signed file (F3-7). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/regulatory_report.receipt_attached/v1",
  "title": "regulatory_report.receipt_attached",
  "description": "The regulator's receipt of a submitted report was kept, write-once, beside the signed file (F3-7).",
  "x-kulpay": {
    "aggregate": "regulatory_run",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "run_id",
    "code",
    "receipt_file_id",
    "attached_by"
  ],
  "properties": {
    "run_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "receipt_file_id": {
      "type": "string"
    },
    "attached_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

regulatory_report.submitted

A reconciled regulatory report was submitted to the regulator once a second person approved (F3-7): submitted_by prepared it, approved_by approved it, by channel with the reference the submission got; the report is kept until retain_until. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/regulatory_report.submitted/v2",
  "title": "regulatory_report.submitted",
  "description": "A reconciled regulatory report was submitted to the regulator once a second person approved (F3-7): submitted_by prepared it, approved_by approved it, by channel with the reference the submission got; the report is kept until retain_until.",
  "x-kulpay": {
    "aggregate": "regulatory_run",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "run_id",
    "code",
    "sha256",
    "submitted_by",
    "approved_by",
    "channel",
    "retain_until"
  ],
  "properties": {
    "run_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "submitted_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    },
    "channel": {
      "enum": [
        "portal",
        "xml_upload",
        "email",
        "sftp",
        "hand_delivery"
      ]
    },
    "reference": {
      "type": "string"
    },
    "retain_until": {
      "type": "string",
      "format": "date"
    }
  },
  "additionalProperties": false
}

request events

EventWebhookTerminalFires whenWhat to do
request.idempotent_replay v1 no no A request was answered from its Idempotency-Key (the stored response) rather than run again. None: this is a client retry working as designed.

request.idempotent_replay

A request was answered from its Idempotency-Key (the stored response) rather than run again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/request.idempotent_replay/v1",
  "title": "request.idempotent_replay",
  "description": "A request was answered from its Idempotency-Key (the stored response) rather than run again.",
  "x-kulpay": {
    "aggregate": "request",
    "webhook": false,
    "terminal": false,
    "act": "None: this is a client retry working as designed."
  },
  "type": "object",
  "required": [
    "idempotency_key",
    "method",
    "path"
  ],
  "properties": {
    "idempotency_key": {
      "type": "string"
    },
    "method": {
      "type": "string"
    },
    "path": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

risk_blocklist events

EventWebhookTerminalFires whenWhat to do
risk.blocklist.added v1 no no A value (kept as a hash) was put on a risk block list. None.
risk.blocklist.removed v1 no no A value was taken off a risk block list. None.

risk.blocklist.added

A value (kept as a hash) was put on a risk block list. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/risk.blocklist.added/v1",
  "title": "risk.blocklist.added",
  "description": "A value (kept as a hash) was put on a risk block list.",
  "x-kulpay": {
    "aggregate": "risk_blocklist",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "block_id",
    "list",
    "label"
  ],
  "properties": {
    "block_id": {
      "type": "string"
    },
    "list": {
      "enum": [
        "beneficiary",
        "phone",
        "card"
      ]
    },
    "label": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

risk.blocklist.removed

A value was taken off a risk block list. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/risk.blocklist.removed/v1",
  "title": "risk.blocklist.removed",
  "description": "A value was taken off a risk block list.",
  "x-kulpay": {
    "aggregate": "risk_blocklist",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "block_id"
  ],
  "properties": {
    "block_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "removed_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

risk_rules events

EventWebhookTerminalFires whenWhat to do
risk.rules.activated v1 no no A risk rules version was approved and is now in force. None.
risk.rules.version_created v1 no no A new version of the risk rules was prepared; a second person approves it. Approve or reject it (POST /v1/admin/approvals/{id}/decide).

risk.rules.activated

A risk rules version was approved and is now in force. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/risk.rules.activated/v1",
  "title": "risk.rules.activated",
  "description": "A risk rules version was approved and is now in force.",
  "x-kulpay": {
    "aggregate": "risk_rules",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "version",
    "rules_id"
  ],
  "properties": {
    "version": {
      "type": "integer"
    },
    "rules_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

risk.rules.version_created

A new version of the risk rules was prepared; a second person approves it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/risk.rules.version_created/v1",
  "title": "risk.rules.version_created",
  "description": "A new version of the risk rules was prepared; a second person approves it.",
  "x-kulpay": {
    "aggregate": "risk_rules",
    "webhook": false,
    "terminal": false,
    "act": "Approve or reject it (POST /v1/admin/approvals/{id}/decide)."
  },
  "type": "object",
  "required": [
    "version",
    "rules_id"
  ],
  "properties": {
    "version": {
      "type": "integer"
    },
    "rules_id": {
      "type": "string"
    },
    "rules": {
      "type": "integer"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

routing_rules events

EventWebhookTerminalFires whenWhat to do
routing.rules.activated v1 no no A routing rules version was approved and is now in force; the previous one is superseded. None.
routing.rules.rejected v1 no no A proposed routing rules version was rejected by its checker. None.
routing.rules.version_created v1 no no A new version of the routing rules was prepared; a second person approves it. Approve or reject it (POST /v1/admin/approvals/{id}/decide), after a dry run.

routing.rules.activated

A routing rules version was approved and is now in force; the previous one is superseded. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/routing.rules.activated/v1",
  "title": "routing.rules.activated",
  "description": "A routing rules version was approved and is now in force; the previous one is superseded.",
  "x-kulpay": {
    "aggregate": "routing_rules",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "version",
    "rules_id"
  ],
  "properties": {
    "version": {
      "type": "integer"
    },
    "rules_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

routing.rules.rejected

A proposed routing rules version was rejected by its checker. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/routing.rules.rejected/v1",
  "title": "routing.rules.rejected",
  "description": "A proposed routing rules version was rejected by its checker.",
  "x-kulpay": {
    "aggregate": "routing_rules",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "version",
    "rules_id"
  ],
  "properties": {
    "version": {
      "type": "integer"
    },
    "rules_id": {
      "type": "string"
    },
    "rejected_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

routing.rules.version_created

A new version of the routing rules was prepared; a second person approves it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/routing.rules.version_created/v1",
  "title": "routing.rules.version_created",
  "description": "A new version of the routing rules was prepared; a second person approves it.",
  "x-kulpay": {
    "aggregate": "routing_rules",
    "webhook": false,
    "terminal": false,
    "act": "Approve or reject it (POST /v1/admin/approvals/{id}/decide), after a dry run."
  },
  "type": "object",
  "required": [
    "version",
    "rules_id"
  ],
  "properties": {
    "version": {
      "type": "integer"
    },
    "rules_id": {
      "type": "string"
    },
    "rules": {
      "type": "integer"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    },
    "rollback_of": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

safeguarding_check events

EventWebhookTerminalFires whenWhat to do
safeguarding.check.passed v1 no yes The business day's safeguarding check passed for a currency (PSP-10.4): the safeguarding accounts' statement balances cover the ledger's client-funds liability. The signed report (CSV, PDF) is kept write-once. None.
safeguarding.check.shortfall v1 no yes Critical: the business day's safeguarding check found a shortfall of any amount, or a safeguarding account's statement missing, for a currency (PSP-10.4). The report is marked non-compliant and compliance is emailed the same day. Critical: compliance acts the same day; fund the safeguarding account or find the missing statement.

safeguarding.check.passed

The business day's safeguarding check passed for a currency (PSP-10.4): the safeguarding accounts' statement balances cover the ledger's client-funds liability. The signed report (CSV, PDF) is kept write-once. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/safeguarding.check.passed/v1",
  "title": "safeguarding.check.passed",
  "description": "The business day's safeguarding check passed for a currency (PSP-10.4): the safeguarding accounts' statement balances cover the ledger's client-funds liability. The signed report (CSV, PDF) is kept write-once.",
  "x-kulpay": {
    "aggregate": "safeguarding_check",
    "webhook": false,
    "terminal": true,
    "act": "None."
  },
  "type": "object",
  "required": [
    "check_id",
    "date",
    "currency",
    "held_minor",
    "owed_minor",
    "difference_minor",
    "compliant"
  ],
  "properties": {
    "check_id": {
      "type": "string"
    },
    "date": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "held_minor": {
      "type": "integer"
    },
    "owed_minor": {
      "type": "integer"
    },
    "difference_minor": {
      "type": "integer"
    },
    "compliant": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

safeguarding.check.shortfall

Critical: the business day's safeguarding check found a shortfall of any amount, or a safeguarding account's statement missing, for a currency (PSP-10.4). The report is marked non-compliant and compliance is emailed the same day. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/safeguarding.check.shortfall/v1",
  "title": "safeguarding.check.shortfall",
  "description": "Critical: the business day's safeguarding check found a shortfall of any amount, or a safeguarding account's statement missing, for a currency (PSP-10.4). The report is marked non-compliant and compliance is emailed the same day.",
  "x-kulpay": {
    "aggregate": "safeguarding_check",
    "webhook": false,
    "terminal": true,
    "act": "Critical: compliance acts the same day; fund the safeguarding account or find the missing statement."
  },
  "type": "object",
  "required": [
    "check_id",
    "date",
    "currency",
    "held_minor",
    "owed_minor",
    "difference_minor",
    "compliant",
    "severity"
  ],
  "properties": {
    "severity": {
      "type": "string",
      "enum": [
        "critical"
      ]
    },
    "missing_statement": {
      "type": "boolean"
    },
    "check_id": {
      "type": "string"
    },
    "date": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "held_minor": {
      "type": "integer"
    },
    "owed_minor": {
      "type": "integer"
    },
    "difference_minor": {
      "type": "integer"
    },
    "compliant": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

screening_lists events

EventWebhookTerminalFires whenWhat to do
screening.lists.updated v1 no no A new version of the sanctions and PEP lists was loaded (F3-6): the UN Consolidated List, OFAC's SDN list and the compliance team's PEP list, with the entries of each. Active partners are screened again against it; a new match records psp.screening.hit. Review any psp.screening.hit that follows.

screening.lists.updated

A new version of the sanctions and PEP lists was loaded (F3-6): the UN Consolidated List, OFAC's SDN list and the compliance team's PEP list, with the entries of each. Active partners are screened again against it; a new match records psp.screening.hit. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/screening.lists.updated/v1",
  "title": "screening.lists.updated",
  "description": "A new version of the sanctions and PEP lists was loaded (F3-6): the UN Consolidated List, OFAC's SDN list and the compliance team's PEP list, with the entries of each. Active partners are screened again against it; a new match records psp.screening.hit.",
  "x-kulpay": {
    "aggregate": "screening_lists",
    "webhook": false,
    "terminal": false,
    "act": "Review any psp.screening.hit that follows."
  },
  "type": "object",
  "required": [
    "version",
    "entries"
  ],
  "properties": {
    "version": {
      "type": "string"
    },
    "entries": {
      "type": "integer"
    },
    "un": {
      "type": "integer"
    },
    "ofac": {
      "type": "integer"
    },
    "pep": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

service_account events

EventWebhookTerminalFires whenWhat to do
service_account.created v1 no no A service account was created. Audit record. Compare before and after.
service_account.disabled v1 no no A service account was disabled; no further keys can be issued under it. Audit record. Compare before and after.

service_account.created

A service account was created. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/service_account.created/v1",
  "title": "service_account.created",
  "description": "A service account was created.",
  "x-kulpay": {
    "aggregate": "service_account",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/service_account.json"
    },
    "after": {
      "$ref": "kulpay://objects/service_account.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

service_account.disabled

A service account was disabled; no further keys can be issued under it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/service_account.disabled/v1",
  "title": "service_account.disabled",
  "description": "A service account was disabled; no further keys can be issued under it.",
  "x-kulpay": {
    "aggregate": "service_account",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/service_account.json"
    },
    "after": {
      "$ref": "kulpay://objects/service_account.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

settlement_batch events

EventWebhookTerminalFires whenWhat to do
settlement.batch.created v1 yes no A settlement batch was built at the merchant's cycle (PSP-10.3): the movements up to the cut-off that no batch held, grouped into gross (charges), fees, refunds and adjustments, and the net. Collecting model: the net is paid out next (status created), or carried into the next batch when there is nothing to pay (carried_forward). Direct model: the batch records what the PSP should pay and the fees KulPay is owed (recorded). The report lists every line. Fetch the report (GET /v1/settlements/{id}/report) to reconcile the lines.
settlement.batch.failed v1 yes no A settlement batch could not be paid (no destination, the payout refused or failed), or its payout came back after it was paid (PSP-10.3). The net is never lost: it is carried into the next batch as an adjustment. Check the merchant's settlement destination; the next batch carries the amount.
settlement.batch.paid v1 yes yes A settlement batch's net was paid to the merchant's settlement destination (PSP-10.3). The batch is now immutable; a later correction lands in a following batch as an adjustment. Expect the funds at the destination; the payout_id links the transfer.
settlement.batch.paying v1 no no A settlement batch's payout was accepted and waits for the rail's answer: a bank transfer goes in the next batch to the sponsor bank, and the batch becomes paid when the bank debits it (F2-3). bank_reference is the transfer's EndToEndId, as the bank's statement shows it. Nothing: settlement.batch.paid or settlement.batch.failed follows.

settlement.batch.created

A settlement batch was built at the merchant's cycle (PSP-10.3): the movements up to the cut-off that no batch held, grouped into gross (charges), fees, refunds and adjustments, and the net. Collecting model: the net is paid out next (status created), or carried into the next batch when there is nothing to pay (carried_forward). Direct model: the batch records what the PSP should pay and the fees KulPay is owed (recorded). The report lists every line. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/settlement.batch.created/v1",
  "title": "settlement.batch.created",
  "description": "A settlement batch was built at the merchant's cycle (PSP-10.3): the movements up to the cut-off that no batch held, grouped into gross (charges), fees, refunds and adjustments, and the net. Collecting model: the net is paid out next (status created), or carried into the next batch when there is nothing to pay (carried_forward). Direct model: the batch records what the PSP should pay and the fees KulPay is owed (recorded). The report lists every line.",
  "x-kulpay": {
    "aggregate": "settlement_batch",
    "webhook": true,
    "terminal": false,
    "act": "Fetch the report (GET /v1/settlements/{id}/report) to reconcile the lines."
  },
  "type": "object",
  "required": [
    "batch_id",
    "account_id",
    "currency",
    "net",
    "status",
    "gross",
    "fees",
    "refunds",
    "adjustments",
    "line_count",
    "cycle_date",
    "period_end",
    "funds_flow"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "net": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "created",
        "paying",
        "paid",
        "failed",
        "carried_forward",
        "recorded"
      ]
    },
    "gross": {
      "type": "string"
    },
    "fees": {
      "type": "string"
    },
    "refunds": {
      "type": "string"
    },
    "adjustments": {
      "type": "string"
    },
    "line_count": {
      "type": "integer"
    },
    "cycle_date": {
      "type": "string"
    },
    "period_end": {
      "type": "string"
    },
    "funds_flow": {
      "type": "string",
      "enum": [
        "collecting",
        "direct"
      ]
    }
  },
  "additionalProperties": false
}

settlement.batch.failed

A settlement batch could not be paid (no destination, the payout refused or failed), or its payout came back after it was paid (PSP-10.3). The net is never lost: it is carried into the next batch as an adjustment. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/settlement.batch.failed/v1",
  "title": "settlement.batch.failed",
  "description": "A settlement batch could not be paid (no destination, the payout refused or failed), or its payout came back after it was paid (PSP-10.3). The net is never lost: it is carried into the next batch as an adjustment.",
  "x-kulpay": {
    "aggregate": "settlement_batch",
    "webhook": true,
    "terminal": false,
    "act": "Check the merchant's settlement destination; the next batch carries the amount."
  },
  "type": "object",
  "required": [
    "batch_id",
    "account_id",
    "currency",
    "net",
    "status",
    "reason"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "net": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "created",
        "paying",
        "paid",
        "failed",
        "carried_forward",
        "recorded"
      ]
    },
    "reason": {
      "type": "string"
    },
    "payout_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

settlement.batch.paid

A settlement batch's net was paid to the merchant's settlement destination (PSP-10.3). The batch is now immutable; a later correction lands in a following batch as an adjustment. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/settlement.batch.paid/v1",
  "title": "settlement.batch.paid",
  "description": "A settlement batch's net was paid to the merchant's settlement destination (PSP-10.3). The batch is now immutable; a later correction lands in a following batch as an adjustment.",
  "x-kulpay": {
    "aggregate": "settlement_batch",
    "webhook": true,
    "terminal": true,
    "act": "Expect the funds at the destination; the payout_id links the transfer."
  },
  "type": "object",
  "required": [
    "batch_id",
    "account_id",
    "currency",
    "net",
    "status",
    "payout_id"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "net": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "created",
        "paying",
        "paid",
        "failed",
        "carried_forward",
        "recorded"
      ]
    },
    "payout_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

settlement.batch.paying

A settlement batch's payout was accepted and waits for the rail's answer: a bank transfer goes in the next batch to the sponsor bank, and the batch becomes paid when the bank debits it (F2-3). bank_reference is the transfer's EndToEndId, as the bank's statement shows it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/settlement.batch.paying/v1",
  "title": "settlement.batch.paying",
  "description": "A settlement batch's payout was accepted and waits for the rail's answer: a bank transfer goes in the next batch to the sponsor bank, and the batch becomes paid when the bank debits it (F2-3). bank_reference is the transfer's EndToEndId, as the bank's statement shows it.",
  "x-kulpay": {
    "aggregate": "settlement_batch",
    "webhook": false,
    "terminal": false,
    "act": "Nothing: settlement.batch.paid or settlement.batch.failed follows."
  },
  "type": "object",
  "required": [
    "batch_id",
    "account_id",
    "currency",
    "net",
    "status",
    "payout_id"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "net": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "paying"
      ]
    },
    "payout_id": {
      "type": "string"
    },
    "bank_reference": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

transaction events

EventWebhookTerminalFires whenWhat to do
transaction.created v1 no no An originating PSP submitted a transaction through POST /v1/psp/transactions (PSP-7.2): a sale, payout or refund, carrying the originator's own reference. It is recorded with the payment, payout or refund it created, whose own events follow it; the envelope's direction is inbound and its pattern inbound_request. None. The originator is told the outcome by the payment, payout or refund webhooks.

transaction.created

An originating PSP submitted a transaction through POST /v1/psp/transactions (PSP-7.2): a sale, payout or refund, carrying the originator's own reference. It is recorded with the payment, payout or refund it created, whose own events follow it; the envelope's direction is inbound and its pattern inbound_request. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.created/v1",
  "title": "transaction.created",
  "description": "An originating PSP submitted a transaction through POST /v1/psp/transactions (PSP-7.2): a sale, payout or refund, carrying the originator's own reference. It is recorded with the payment, payout or refund it created, whose own events follow it; the envelope's direction is inbound and its pattern inbound_request.",
  "x-kulpay": {
    "aggregate": "transaction",
    "webhook": false,
    "terminal": false,
    "act": "None. The originator is told the outcome by the payment, payout or refund webhooks."
  },
  "type": "object",
  "required": [
    "transaction_id",
    "type",
    "originator_reference",
    "amount",
    "currency"
  ],
  "properties": {
    "transaction_id": {
      "type": "string"
    },
    "type": {
      "enum": [
        "sale",
        "payout",
        "refund"
      ]
    },
    "originator_reference": {
      "type": "string"
    },
    "amount": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "method": {
      "type": "string"
    },
    "original_transaction_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}