DOCS 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. 407 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.cash_in_incoming.set v1 no no Whether an account's cash deposits initiated against a customer's reference are also recorded as incoming transfers was set. Expect (or stop expecting) incoming_transfer events for cash deposits.
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.
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.cash_in_incoming.set

Whether an account's cash deposits initiated against a customer's reference are also recorded as incoming transfers was set. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/account.cash_in_incoming.set/v1",
  "title": "account.cash_in_incoming.set",
  "description": "Whether an account's cash deposits initiated against a customer's reference are also recorded as incoming transfers was set.",
  "x-kulpay": {
    "aggregate": "account",
    "webhook": false,
    "terminal": false,
    "act": "Expect (or stop expecting) incoming_transfer events for cash deposits."
  },
  "type": "object",
  "required": [
    "account_id",
    "enabled"
  ],
  "properties": {
    "account_id": {
      "type": "string"
    },
    "enabled": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

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
}

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
}

bill_autopay events

EventWebhookTerminalFires whenWhat to do
bill_autopay.attempt_failed v1 yes no An autopay debit failed and will be tried again at next_attempt_at (BIL-5.10). Tell the payer the debit failed (for example: top up the wallet or the account).
bill_autopay.failed v1 yes no Autopay gave up on an invoice after its attempts (BIL-5.10): the invoice stays open for the payer to pay. Ask the payer to pay the invoice.
bill_autopay.mandate_cancelled v1 yes no A subscription's autopay mandate was cancelled (BIL-5.10): by the payer, or because they opted out of the subscription. Its scheduled invoices are not debited. Tell the payer autopay is off.
bill_autopay.mandate_set v1 yes no A payer gave (or changed) the mandate to pay a subscription's invoices automatically (BIL-5.10): how, up to how much per invoice, how many days before the due date, under which terms. Keep the mandate with the payer's consent; their open invoices are scheduled.
bill_autopay.processing v1 no no An autopay debit is in flight (BIL-5.10): its collection or the biller's answer is pending. None.
bill_autopay.scheduled v1 yes no An invoice will be debited under the payer's autopay mandate (BIL-5.10): the pre-debit notice, sent at least bills.autopay_notice_hours before the debit. Notify the payer of the amount and date before the debit; they may skip it (POST …/autopay/skip).
bill_autopay.skipped v1 yes no An invoice will not be debited under the mandate (BIL-5.10): above its limit (over_limit), skipped by the payer (payer_skipped), the mandate cancelled, or the invoice paid, cancelled or expired meanwhile (invoice_<status>). Tell the payer when they must pay it themselves (over_limit).
bill_autopay.succeeded v1 yes no An invoice was paid under the payer's autopay mandate (BIL-5.10). With funding balance, debit the payer's own account for it. Debit the payer (funding balance) and send them the receipt.

bill_autopay.attempt_failed

An autopay debit failed and will be tried again at next_attempt_at (BIL-5.10). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_autopay.attempt_failed/v1",
  "title": "bill_autopay.attempt_failed",
  "description": "An autopay debit failed and will be tried again at next_attempt_at (BIL-5.10).",
  "x-kulpay": {
    "aggregate": "bill_autopay",
    "webhook": true,
    "terminal": false,
    "act": "Tell the payer the debit failed (for example: top up the wallet or the account)."
  },
  "type": "object",
  "required": [
    "mandate_id",
    "subscription_id",
    "payer_id",
    "status",
    "run_id",
    "invoice_id",
    "amount_minor",
    "currency",
    "scheduled_for",
    "attempts",
    "failure_code",
    "next_attempt_at"
  ],
  "properties": {
    "mandate_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "cancelled"
      ],
      "description": "The mandate's status."
    },
    "run_id": {
      "type": "string"
    },
    "invoice_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "scheduled_for": {
      "type": "string",
      "description": "When the invoice is (or was) debited, RFC 3339."
    },
    "attempts": {
      "type": "integer"
    },
    "bill_payment_id": {
      "type": "string"
    },
    "failure_code": {
      "type": "string"
    },
    "failure_message": {
      "type": "string"
    },
    "next_attempt_at": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_autopay.failed

Autopay gave up on an invoice after its attempts (BIL-5.10): the invoice stays open for the payer to pay. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_autopay.failed/v1",
  "title": "bill_autopay.failed",
  "description": "Autopay gave up on an invoice after its attempts (BIL-5.10): the invoice stays open for the payer to pay.",
  "x-kulpay": {
    "aggregate": "bill_autopay",
    "webhook": true,
    "terminal": false,
    "act": "Ask the payer to pay the invoice."
  },
  "type": "object",
  "required": [
    "mandate_id",
    "subscription_id",
    "payer_id",
    "status",
    "run_id",
    "invoice_id",
    "amount_minor",
    "currency",
    "scheduled_for",
    "attempts",
    "failure_code"
  ],
  "properties": {
    "mandate_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "cancelled"
      ],
      "description": "The mandate's status."
    },
    "run_id": {
      "type": "string"
    },
    "invoice_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "scheduled_for": {
      "type": "string",
      "description": "When the invoice is (or was) debited, RFC 3339."
    },
    "attempts": {
      "type": "integer"
    },
    "bill_payment_id": {
      "type": "string"
    },
    "failure_code": {
      "type": "string"
    },
    "failure_message": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_autopay.mandate_cancelled

A subscription's autopay mandate was cancelled (BIL-5.10): by the payer, or because they opted out of the subscription. Its scheduled invoices are not debited. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_autopay.mandate_cancelled/v1",
  "title": "bill_autopay.mandate_cancelled",
  "description": "A subscription's autopay mandate was cancelled (BIL-5.10): by the payer, or because they opted out of the subscription. Its scheduled invoices are not debited.",
  "x-kulpay": {
    "aggregate": "bill_autopay",
    "webhook": true,
    "terminal": false,
    "act": "Tell the payer autopay is off."
  },
  "type": "object",
  "required": [
    "mandate_id",
    "subscription_id",
    "payer_id",
    "status",
    "reason"
  ],
  "properties": {
    "mandate_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "cancelled"
      ],
      "description": "The mandate's status."
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_autopay.mandate_set

A payer gave (or changed) the mandate to pay a subscription's invoices automatically (BIL-5.10): how, up to how much per invoice, how many days before the due date, under which terms. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_autopay.mandate_set/v1",
  "title": "bill_autopay.mandate_set",
  "description": "A payer gave (or changed) the mandate to pay a subscription's invoices automatically (BIL-5.10): how, up to how much per invoice, how many days before the due date, under which terms.",
  "x-kulpay": {
    "aggregate": "bill_autopay",
    "webhook": true,
    "terminal": false,
    "act": "Keep the mandate with the payer's consent; their open invoices are scheduled."
  },
  "type": "object",
  "required": [
    "mandate_id",
    "subscription_id",
    "payer_id",
    "status",
    "funding",
    "max_amount_minor",
    "currency",
    "days_before_due",
    "consent_version"
  ],
  "properties": {
    "mandate_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "cancelled"
      ],
      "description": "The mandate's status."
    },
    "funding": {
      "enum": [
        "balance",
        "collect"
      ]
    },
    "max_amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "days_before_due": {
      "type": "integer"
    },
    "consent_version": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_autopay.processing

An autopay debit is in flight (BIL-5.10): its collection or the biller's answer is pending. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_autopay.processing/v1",
  "title": "bill_autopay.processing",
  "description": "An autopay debit is in flight (BIL-5.10): its collection or the biller's answer is pending.",
  "x-kulpay": {
    "aggregate": "bill_autopay",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "mandate_id",
    "subscription_id",
    "payer_id",
    "status",
    "run_id",
    "invoice_id",
    "amount_minor",
    "currency",
    "scheduled_for",
    "attempts"
  ],
  "properties": {
    "mandate_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "cancelled"
      ],
      "description": "The mandate's status."
    },
    "run_id": {
      "type": "string"
    },
    "invoice_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "scheduled_for": {
      "type": "string",
      "description": "When the invoice is (or was) debited, RFC 3339."
    },
    "attempts": {
      "type": "integer"
    },
    "bill_payment_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_autopay.scheduled

An invoice will be debited under the payer's autopay mandate (BIL-5.10): the pre-debit notice, sent at least bills.autopay_notice_hours before the debit. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_autopay.scheduled/v1",
  "title": "bill_autopay.scheduled",
  "description": "An invoice will be debited under the payer's autopay mandate (BIL-5.10): the pre-debit notice, sent at least bills.autopay_notice_hours before the debit.",
  "x-kulpay": {
    "aggregate": "bill_autopay",
    "webhook": true,
    "terminal": false,
    "act": "Notify the payer of the amount and date before the debit; they may skip it (POST …/autopay/skip)."
  },
  "type": "object",
  "required": [
    "mandate_id",
    "subscription_id",
    "payer_id",
    "status",
    "run_id",
    "invoice_id",
    "amount_minor",
    "currency",
    "scheduled_for",
    "biller",
    "number",
    "due_date"
  ],
  "properties": {
    "mandate_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "cancelled"
      ],
      "description": "The mandate's status."
    },
    "run_id": {
      "type": "string"
    },
    "invoice_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "scheduled_for": {
      "type": "string",
      "description": "When the invoice is (or was) debited, RFC 3339."
    },
    "biller": {
      "type": "string"
    },
    "number": {
      "type": "string"
    },
    "due_date": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

bill_autopay.skipped

An invoice will not be debited under the mandate (BIL-5.10): above its limit (over_limit), skipped by the payer (payer_skipped), the mandate cancelled, or the invoice paid, cancelled or expired meanwhile (invoice_<status>). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_autopay.skipped/v1",
  "title": "bill_autopay.skipped",
  "description": "An invoice will not be debited under the mandate (BIL-5.10): above its limit (over_limit), skipped by the payer (payer_skipped), the mandate cancelled, or the invoice paid, cancelled or expired meanwhile (invoice_<status>).",
  "x-kulpay": {
    "aggregate": "bill_autopay",
    "webhook": true,
    "terminal": false,
    "act": "Tell the payer when they must pay it themselves (over_limit)."
  },
  "type": "object",
  "required": [
    "mandate_id",
    "subscription_id",
    "payer_id",
    "status",
    "run_id",
    "invoice_id",
    "amount_minor",
    "currency",
    "scheduled_for",
    "reason"
  ],
  "properties": {
    "mandate_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "cancelled"
      ],
      "description": "The mandate's status."
    },
    "run_id": {
      "type": "string"
    },
    "invoice_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "scheduled_for": {
      "type": "string",
      "description": "When the invoice is (or was) debited, RFC 3339."
    },
    "reason": {
      "type": "string"
    },
    "max_amount_minor": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

bill_autopay.succeeded

An invoice was paid under the payer's autopay mandate (BIL-5.10). With funding balance, debit the payer's own account for it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_autopay.succeeded/v1",
  "title": "bill_autopay.succeeded",
  "description": "An invoice was paid under the payer's autopay mandate (BIL-5.10). With funding balance, debit the payer's own account for it.",
  "x-kulpay": {
    "aggregate": "bill_autopay",
    "webhook": true,
    "terminal": false,
    "act": "Debit the payer (funding balance) and send them the receipt."
  },
  "type": "object",
  "required": [
    "mandate_id",
    "subscription_id",
    "payer_id",
    "status",
    "run_id",
    "invoice_id",
    "amount_minor",
    "currency",
    "scheduled_for",
    "attempts",
    "bill_payment_id"
  ],
  "properties": {
    "mandate_id": {
      "type": "string"
    },
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "cancelled"
      ],
      "description": "The mandate's status."
    },
    "run_id": {
      "type": "string"
    },
    "invoice_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "scheduled_for": {
      "type": "string",
      "description": "When the invoice is (or was) debited, RFC 3339."
    },
    "attempts": {
      "type": "integer"
    },
    "bill_payment_id": {
      "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 v4 no no A channel paid a quote (BIL-2.4), from its balance, by collecting from the payer first, or with a transfer the payer made at a partner citing the invoice's payment reference (BIL-5.8). v3 adds funding transfer and the incoming transfer that funds it. v4 adds funding deposit: paid at a partner's counter or app (BIL-5.11), and the deposit that funds it. 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 v1 yes yes A bill payment collected from the payer first could not be delivered (BIL-2.4): the collection was refunded to the payer. 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.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 v2 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. 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, by collecting from the payer first, or with a transfer the payer made at a partner citing the invoice's payment reference (BIL-5.8). v3 adds funding transfer and the incoming transfer that funds it. v4 adds funding deposit: paid at a partner's counter or app (BIL-5.11), and the deposit that funds it. Schema (JSON)

Schema v4
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.accepted/v4",
  "title": "bill_payment.accepted",
  "description": "A channel paid a quote (BIL-2.4), from its balance, by collecting from the payer first, or with a transfer the payer made at a partner citing the invoice's payment reference (BIL-5.8). v3 adds funding transfer and the incoming transfer that funds it. v4 adds funding deposit: paid at a partner's counter or app (BIL-5.11), and the deposit that funds it.",
  "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",
        "transfer",
        "deposit"
      ]
    },
    "incoming_transfer_id": {
      "type": "string",
      "description": "The partner's incoming transfer that funds the payment (funding transfer)."
    },
    "cash_deposit_id": {
      "type": "string",
      "description": "The partner's confirmed deposit that funds the payment (funding deposit)."
    }
  },
  "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 could not be delivered (BIL-2.4): the collection was refunded to the payer. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.refunded/v1",
  "title": "bill_payment.refunded",
  "description": "A bill payment collected from the payer first could not be delivered (BIL-2.4): the collection was refunded to the payer.",
  "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"
      ]
    },
    "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.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. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_payment.succeeded/v2",
  "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.",
  "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"
            ]
          }
        }
      },
      "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_integration_delivery events

EventWebhookTerminalFires whenWhat to do
bill_integration.delivery_exhausted v1 no no A product's payment notification or daily report to its LSP was not accepted after every retry (24 h) (BIL-5.16). The body is kept: it can be sent again. Check the LSP's endpoint with the biller, then send it again (POST …/integration/deliveries/{id}/retry).

bill_integration.delivery_exhausted

A product's payment notification or daily report to its LSP was not accepted after every retry (24 h) (BIL-5.16). The body is kept: it can be sent again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_integration.delivery_exhausted/v1",
  "title": "bill_integration.delivery_exhausted",
  "description": "A product's payment notification or daily report to its LSP was not accepted after every retry (24 h) (BIL-5.16). The body is kept: it can be sent again.",
  "x-kulpay": {
    "aggregate": "bill_integration_delivery",
    "webhook": false,
    "terminal": false,
    "act": "Check the LSP's endpoint with the biller, then send it again (POST …/integration/deliveries/{id}/retry)."
  },
  "type": "object",
  "required": [
    "delivery_id",
    "integration_id",
    "kind",
    "subject_id",
    "attempts"
  ],
  "properties": {
    "delivery_id": {
      "type": "string"
    },
    "integration_id": {
      "type": "string"
    },
    "kind": {
      "enum": [
        "notification",
        "report"
      ]
    },
    "subject_id": {
      "type": "string"
    },
    "attempts": {
      "type": "integer"
    },
    "response_code": {
      "type": "integer"
    },
    "last_error": {
      "type": "string"
    }
  },
  "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 v2 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. Mark the customer's invoice paid; the money arrives with a remittance.
bill_invoice.partially_paid v1 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. Show the customer what remains.
bill_invoice.payment_link_issued v1 yes no The biller reissued an invoice's payment link (BIL-5.9): the previous link no longer works. The new link is in the API response, not in the event. Send the payer the new link; the old one is withdrawn.
bill_invoice.payment_received v3 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 adds funding transfer: paid at a bank or PSP quoting the invoice's payment reference (BIL-5.8). v3 adds funding deposit: paid at a partner's counter or app (BIL-5.11). 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 v4 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. v3 (BIL-5.12): the invoice's QR and its payment reference, to print on the invoice. v4: the QR is the invoice's payment link (qr_url), which any phone camera opens; the EMVCo payload is gone. 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.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. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.paid/v2",
  "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.",
  "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"
                ]
              }
            }
          },
          "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. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.partially_paid/v1",
  "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.",
  "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"
                ]
              }
            }
          },
          "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
}

The biller reissued an invoice's payment link (BIL-5.9): the previous link no longer works. The new link is in the API response, not in the event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.payment_link_issued/v1",
  "title": "bill_invoice.payment_link_issued",
  "description": "The biller reissued an invoice's payment link (BIL-5.9): the previous link no longer works. The new link is in the API response, not in the event.",
  "x-kulpay": {
    "aggregate": "bill_invoice",
    "webhook": true,
    "terminal": false,
    "act": "Send the payer the new link; the old one is withdrawn."
  },
  "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": [
        "mode"
      ],
      "properties": {
        "mode": {
          "enum": [
            "live",
            "test"
          ]
        }
      },
      "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 adds funding transfer: paid at a bank or PSP quoting the invoice's payment reference (BIL-5.8). v3 adds funding deposit: paid at a partner's counter or app (BIL-5.11). Schema (JSON)

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.payment_received/v3",
  "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 adds funding transfer: paid at a bank or PSP quoting the invoice's payment reference (BIL-5.8). v3 adds funding deposit: paid at a partner's counter or app (BIL-5.11).",
  "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",
            "transfer",
            "deposit"
          ]
        },
        "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"
                ]
              }
            }
          },
          "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. v3 (BIL-5.12): the invoice's QR and its payment reference, to print on the invoice. v4: the QR is the invoice's payment link (qr_url), which any phone camera opens; the EMVCo payload is gone. Schema (JSON)

Schema v4
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_invoice.presented/v4",
  "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. v3 (BIL-5.12): the invoice's QR and its payment reference, to print on the invoice. v4: the QR is the invoice's payment link (qr_url), which any phone camera opens; the EMVCo payload is gone.",
  "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"
        },
        "qr_url": {
          "type": "string",
          "description": "What the invoice's QR code encodes: its payment link, which any phone camera opens (render it as a QR code, error correction M)."
        },
        "payment_reference": {
          "type": "string",
          "description": "The ISO 11649 RF reference, grouped by four."
        }
      },
      "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.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.
biller.created v1 no no An operator registered a biller (BIL-1.1). It starts as a draft: channels see it once a second operator activates it. Configure its products, then request its activation.
biller.integration_status v1 yes no The technical status of a biller KulOffice onboarded changed at KulPay (KO-1), told to KulOffice on its own account, in both modes: sandbox when KulOffice's approval created it (its LSP in technical setup) and while it is certified, certified when its certification passed, live when its LSP entered pilot or live (four eyes), incident when its LSP was suspended, offboarded when it is offboarding. status is the biller's effective status: active only when KulOffice has it approved and it is live here. KulOffice shows the integration status on the biller's record.
biller.status_changed v2 no no A biller was activated, suspended or retired with the approval of a second operator (BIL-1.1). A suspended biller refuses new payments; payments in flight complete. A biller KulOffice onboarded (KO-1) changes with no approval of its own: basis says what moved it (origin: KulOffice's commercial status; lifecycle: its LSP's technical status); approval_id is then absent. Channels refresh their biller listing.
biller.technical_contact_replaced v1 yes no The business replaced the technical contact of a biller KulOffice onboarded, in the KulPay portal (KO-2): the previous contact's access is revoked (they consented), the new one is the portal's admin. Told to KulOffice on its own account, in both modes, so its record (the contacts are KulOffice's) follows. KulOffice replaces the technical contact on the biller's record and keeps the previous one as revoked.
biller.updated v1 no no A biller's details changed (BIL-1.1): name, category, logo, support contact or linked organisation. 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
}

biller.created

An operator registered a biller (BIL-1.1). It starts as a draft: channels see it once a second operator activates it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.created/v1",
  "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.",
  "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"
      ]
    }
  },
  "additionalProperties": false
}

biller.integration_status

The technical status of a biller KulOffice onboarded changed at KulPay (KO-1), told to KulOffice on its own account, in both modes: sandbox when KulOffice's approval created it (its LSP in technical setup) and while it is certified, certified when its certification passed, live when its LSP entered pilot or live (four eyes), incident when its LSP was suspended, offboarded when it is offboarding. status is the biller's effective status: active only when KulOffice has it approved and it is live here. 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 KulOffice onboarded changed at KulPay (KO-1), told to KulOffice on its own account, in both modes: sandbox when KulOffice's approval created it (its LSP in technical setup) and while it is certified, certified when its certification passed, live when its LSP entered pilot or live (four eyes), incident when its LSP was suspended, offboarded when it is offboarding. status is the biller's effective status: active only when KulOffice has it approved and it is live here.",
  "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 (the external_ref it was provisioned under)."
    },
    "psp_id": {
      "type": "string",
      "description": "The biller's organisation at KulPay: its LSP."
    },
    "technical_status": {
      "enum": [
        "sandbox",
        "certified",
        "live",
        "incident",
        "offboarded"
      ]
    },
    "status": {
      "enum": [
        "draft",
        "active",
        "suspended",
        "retired"
      ]
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

biller.status_changed

A biller was activated, suspended or retired with the approval of a second operator (BIL-1.1). A suspended biller refuses new payments; payments in flight complete. A biller KulOffice onboarded (KO-1) changes with no approval of its own: basis says what moved it (origin: KulOffice's commercial status; lifecycle: its LSP's technical status); approval_id is then absent. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.status_changed/v2",
  "title": "biller.status_changed",
  "description": "A biller was activated, suspended or retired with the approval of a second operator (BIL-1.1). A suspended biller refuses new payments; payments in flight complete. A biller KulOffice onboarded (KO-1) changes with no approval of its own: basis says what moved it (origin: KulOffice's commercial status; lifecycle: its LSP's technical status); approval_id is then absent.",
  "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"
  ],
  "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",
        "origin",
        "lifecycle"
      ],
      "description": "What moved the status: a four-eyes approval (absent means approval), KulOffice, or the LSP's lifecycle."
    }
  },
  "additionalProperties": false
}

biller.technical_contact_replaced

The business replaced the technical contact of a biller KulOffice onboarded, in the KulPay portal (KO-2): the previous contact's access is revoked (they consented), the new one is the portal's admin. Told to KulOffice on its own account, in both modes, so its record (the contacts are KulOffice's) follows. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.technical_contact_replaced/v1",
  "title": "biller.technical_contact_replaced",
  "description": "The business replaced the technical contact of a biller KulOffice onboarded, in the KulPay portal (KO-2): the previous contact's access is revoked (they consented), the new one is the portal's admin. Told to KulOffice on its own account, in both modes, so its record (the contacts are KulOffice's) follows.",
  "x-kulpay": {
    "aggregate": "biller",
    "webhook": true,
    "terminal": false,
    "act": "KulOffice replaces the technical contact on the biller's record and keeps the previous one as revoked."
  },
  "type": "object",
  "required": [
    "biller_id",
    "code",
    "origin",
    "external_id",
    "previous_email",
    "contact",
    "reason",
    "replaced_by",
    "replaced_at",
    "consent"
  ],
  "properties": {
    "biller_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "origin": {
      "enum": [
        "kuloffice"
      ]
    },
    "external_id": {
      "type": "string",
      "description": "The biller's id in KulOffice."
    },
    "psp_id": {
      "type": "string"
    },
    "previous_email": {
      "type": "string",
      "description": "The replaced contact, whose access was revoked."
    },
    "contact": {
      "type": "object",
      "required": [
        "name",
        "email"
      ],
      "properties": {
        "name": {
          "type": "string"
        },
        "email": {
          "type": "string"
        },
        "phone": {
          "type": "string",
          "x-sensitive": "mask",
          "description": "Masked (PSP-12.4): KulOffice keeps the phone it has, or asks the business."
        }
      },
      "additionalProperties": false
    },
    "reason": {
      "type": "string"
    },
    "replaced_by": {
      "type": "string",
      "description": "The portal admin who replaced it (email)."
    },
    "replaced_at": {
      "type": "string",
      "format": "date-time"
    },
    "consent": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

biller.updated

A biller's details changed (BIL-1.1): name, category, logo, support contact or linked organisation. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/biller.updated/v1",
  "title": "biller.updated",
  "description": "A biller's details changed (BIL-1.1): name, category, logo, support contact or linked organisation.",
  "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"
      ]
    }
  },
  "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.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_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.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.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.integration_set v1 no no A product's own integration with its LSP was set in a mode (BIL-5.16): its payment notification (URL, body template, auth) and its daily report (format, columns, file name, URL). Secrets are never in the event. 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.integration_set

A product's own integration with its LSP was set in a mode (BIL-5.16): its payment notification (URL, body template, auth) and its daily report (format, columns, file name, URL). Secrets are never in the event. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_product.integration_set/v1",
  "title": "bill_product.integration_set",
  "description": "A product's own integration with its LSP was set in a mode (BIL-5.16): its payment notification (URL, body template, auth) and its daily report (format, columns, file name, URL). Secrets are never in the event.",
  "x-kulpay": {
    "aggregate": "bill_product",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "product_id",
    "biller_id",
    "mode",
    "version"
  ],
  "properties": {
    "product_id": {
      "type": "string"
    },
    "biller_id": {
      "type": "string"
    },
    "mode": {
      "enum": [
        "live",
        "test"
      ]
    },
    "version": {
      "type": "integer"
    },
    "notify_enabled": {
      "type": "boolean"
    },
    "report_enabled": {
      "type": "boolean"
    },
    "report_format": {
      "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 v1 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. Expect the transfer; reconcile with the report.
bill_remittance.paid v1 yes yes KulPay paid a remittance to the biller by bank transfer, approved by a second person (BIL-5.7). Match the transfer with its bank_reference.

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. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_remittance.created/v1",
  "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.",
  "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"
      ]
    }
  },
  "additionalProperties": false
}

bill_remittance.paid

KulPay paid a remittance to the biller by bank transfer, approved by a second person (BIL-5.7). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_remittance.paid/v1",
  "title": "bill_remittance.paid",
  "description": "KulPay paid a remittance to the biller by bank transfer, approved by a second person (BIL-5.7).",
  "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",
    "approval_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": [
        "pending",
        "paid"
      ]
    },
    "bank_reference": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    }
  },
  "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.offered v1 yes no A biller enrolled a customer whose contact matches a payer of yours who is asked first (enrolments: offer, BIL-5.5): the biller account is offered to them, and nothing of it is delivered until they accept (POST /v1/payers/{id}/subscriptions with their consent) or decline (DELETE /v1/payers/{id}/subscriptions/{sid}). Ask the payer whether they want to receive this biller's bills.
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.offered

A biller enrolled a customer whose contact matches a payer of yours who is asked first (enrolments: offer, BIL-5.5): the biller account is offered to them, and nothing of it is delivered until they accept (POST /v1/payers/{id}/subscriptions with their consent) or decline (DELETE /v1/payers/{id}/subscriptions/{sid}). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/bill_subscription.offered/v1",
  "title": "bill_subscription.offered",
  "description": "A biller enrolled a customer whose contact matches a payer of yours who is asked first (enrolments: offer, BIL-5.5): the biller account is offered to them, and nothing of it is delivered until they accept (POST /v1/payers/{id}/subscriptions with their consent) or decline (DELETE /v1/payers/{id}/subscriptions/{sid}).",
  "x-kulpay": {
    "aggregate": "bill_subscription",
    "webhook": true,
    "terminal": false,
    "act": "Ask the payer whether they want to receive this biller's bills."
  },
  "type": "object",
  "required": [
    "subscription_id",
    "payer_id",
    "external_id",
    "biller",
    "biller_name",
    "status",
    "origin"
  ],
  "properties": {
    "subscription_id": {
      "type": "string"
    },
    "payer_id": {
      "type": "string"
    },
    "external_id": {
      "type": "string"
    },
    "biller": {
      "type": "string"
    },
    "biller_name": {
      "type": "string"
    },
    "account_reference_masked": {
      "type": "string"
    },
    "status": {
      "enum": [
        "offered"
      ]
    },
    "origin": {
      "enum": [
        "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
}

cash_deposit_intent events

EventWebhookTerminalFires whenWhat to do
cash_in.completed v1 yes yes A deposit that pays a bill reached its end (BIL-5.11): completed, the bill paid (its receipt and any token on the bill payment); or failed, the bill not paid and the money in suspense for KulPay's treasury to return. Give the payer the receipt and any token; on failed, tell them the money will be returned.
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: destination_kind, for a deposit that pays a bill (BIL-5.11). The KulPay CMS can show the deposit as expected. None for KulPay.
cash_in.initiation_confirmed v2 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. v2: a deposit that pays a bill (BIL-5.11) is processing once confirmed, or failed when the bill cannot be paid; it carries its bill payment. 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 deposit that pays a bill reached its end (BIL-5.11): completed, the bill paid (its receipt and any token on the bill payment); or failed, the bill not paid and the money in suspense for KulPay's treasury to return. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.completed/v1",
  "title": "cash_in.completed",
  "description": "A deposit that pays a bill reached its end (BIL-5.11): completed, the bill paid (its receipt and any token on the bill payment); or failed, the bill not paid and the money in suspense for KulPay's treasury to return.",
  "x-kulpay": {
    "aggregate": "cash_deposit_intent",
    "webhook": true,
    "terminal": true,
    "act": "Give the payer the receipt and any token; on failed, tell them the money will be returned."
  },
  "type": "object",
  "required": [
    "intent_id",
    "lookup_id",
    "psp_id",
    "amount_minor",
    "currency",
    "status",
    "expires_at",
    "deposit_id"
  ],
  "properties": {
    "intent_id": {
      "type": "string"
    },
    "lookup_id": {
      "type": "string"
    },
    "psp_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "currency": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "initiated",
        "reported",
        "expired",
        "processing",
        "completed",
        "failed"
      ]
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "partner_reference": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    },
    "bill_payment_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "failure_code": {
      "type": "string"
    },
    "failure_message": {
      "type": "string"
    }
  },
  "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: destination_kind, for a deposit that pays a bill (BIL-5.11). 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: destination_kind, for a deposit that pays a bill (BIL-5.11).",
  "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": {
      "type": "string",
      "enum": [
        "initiated",
        "reported",
        "expired",
        "processing",
        "completed",
        "failed"
      ]
    },
    "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"
      ]
    }
  },
  "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. v2: a deposit that pays a bill (BIL-5.11) is processing once confirmed, or failed when the bill cannot be paid; it carries its bill payment. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.initiation_confirmed/v2",
  "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. v2: a deposit that pays a bill (BIL-5.11) is processing once confirmed, or failed when the bill cannot be paid; it carries its bill payment.",
  "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": {
      "type": "string",
      "enum": [
        "initiated",
        "reported",
        "expired",
        "processing",
        "completed",
        "failed"
      ]
    },
    "expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "partner_reference": {
      "type": "string"
    },
    "deposit_id": {
      "type": "string"
    },
    "entry_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "bill_payment_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
}

cash_deposit events

EventWebhookTerminalFires whenWhat to do
cash_in.confirmed v1 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. 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 the partner's confirmation (a bill paid at the counter, BIL-5.11) was found on the statement with the same amount. None.
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 v1 no yes A suspended cash deposit was returned (PSP-7.6), with a second person approving. Send the money back to the depositor through the bank.
cash_in.suspended v1 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). Assign it to an account or return it (POST /v1/admin/cash-deposits/{id}/resolve).

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. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.confirmed/v1",
  "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.",
  "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"
    }
  },
  "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 the partner's confirmation (a bill paid at the counter, BIL-5.11) was found on the statement with the same amount. 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 the partner's confirmation (a bill paid at the counter, BIL-5.11) was found on the statement with the same amount.",
  "x-kulpay": {
    "aggregate": "cash_deposit",
    "webhook": false,
    "terminal": true,
    "act": "None."
  },
  "type": "object",
  "required": [
    "deposit_id",
    "psp_id",
    "account",
    "entry_id",
    "amount_minor",
    "currency",
    "value_date",
    "status",
    "recon_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"
      ]
    },
    "recon_status": {
      "enum": [
        "matched"
      ]
    },
    "line_id": {
      "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. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.returned/v1",
  "title": "cash_in.returned",
  "description": "A suspended cash deposit was returned (PSP-7.6), with a second person approving.",
  "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"
    }
  },
  "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). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/cash_in.suspended/v1",
  "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).",
  "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"
    }
  },
  "additionalProperties": false
}

catalog events

EventWebhookTerminalFires whenWhat to do
catalog.activated v1 no no A back-office catalogue version was approved and is active (SETUP-1), superseding the previous one. Partners are offered its active items from now on; a retired item stays with the partners who already chose it. None: forms, validation and the catalogue API use this version.
catalog.version_created v1 no no A new version of a back-office catalogue (SETUP-1) was proposed: the list partners pick from, such as roles, biller sectors or KYB documents. It waits for a second person; a regulatory catalogue needs one with compliance:approve. Until then the active version applies. Approve or reject it (POST /v1/admin/approvals/{id}/decide).

catalog.activated

A back-office catalogue version was approved and is active (SETUP-1), superseding the previous one. Partners are offered its active items from now on; a retired item stays with the partners who already chose it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/catalog.activated/v1",
  "title": "catalog.activated",
  "description": "A back-office catalogue version was approved and is active (SETUP-1), superseding the previous one. Partners are offered its active items from now on; a retired item stays with the partners who already chose it.",
  "x-kulpay": {
    "aggregate": "catalog",
    "webhook": false,
    "terminal": false,
    "act": "None: forms, validation and the catalogue API use this version."
  },
  "type": "object",
  "required": [
    "catalog",
    "version",
    "version_id",
    "previous_version"
  ],
  "properties": {
    "catalog": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "previous_version": {
      "type": "integer"
    },
    "prepared_by": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

catalog.version_created

A new version of a back-office catalogue (SETUP-1) was proposed: the list partners pick from, such as roles, biller sectors or KYB documents. It waits for a second person; a regulatory catalogue needs one with compliance:approve. Until then the active version applies. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/catalog.version_created/v1",
  "title": "catalog.version_created",
  "description": "A new version of a back-office catalogue (SETUP-1) was proposed: the list partners pick from, such as roles, biller sectors or KYB documents. It waits for a second person; a regulatory catalogue needs one with compliance:approve. Until then the active version applies.",
  "x-kulpay": {
    "aggregate": "catalog",
    "webhook": false,
    "terminal": false,
    "act": "Approve or reject it (POST /v1/admin/approvals/{id}/decide)."
  },
  "type": "object",
  "required": [
    "catalog",
    "version",
    "version_id"
  ],
  "properties": {
    "catalog": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "version_id": {
      "type": "string"
    },
    "items": {
      "type": "integer"
    },
    "regulatory": {
      "type": "boolean"
    },
    "approval_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    }
  },
  "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
}

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.capabilities_blocked v1 no no What a partner's past-due requirements set to block close changed (Épico 27, decision 1): the capabilities now closed (sandbox, pilot, live_payments, payouts, remittances), the ones before, and the requirements past due. An empty list opens them again. The partner or KulPay meets the past-due requirements; the capabilities open again by themselves.
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.exceptions v1 no no A certification run passed every mandatory case but some optional ones failed: the PSP is certified only once two people accept the failed optional cases. An operator prepares the acceptance of the failed optional cases and a second one approves it, or the PSP repeats them.
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.kyb.approved v2 no no KYB was approved: by a second compliance officer at KulPay (maker-checker), or, for an LSP KulOffice onboarded, by KulOffice's own four-eyes approval, which KulPay relies on (KO-1: the same regulated entity). basis says which, decision_ref names KulOffice's decision. 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.partner.reminded v1 no no The partner was reminded by email of the technical setup steps it has not finished, by an operator or by the system after the steps were left idle. The partner finishes the steps in the portal.
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.screening.subject_changed v1 no no A person was added to, or taken off, the list a partner's KYB checks against the sanctions and PEP lists: a director, a beneficial owner or a signatory, from its documents. Check the person against the sanctions and PEP lists.
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.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.capabilities_blocked

What a partner's past-due requirements set to block close changed (Épico 27, decision 1): the capabilities now closed (sandbox, pilot, live_payments, payouts, remittances), the ones before, and the requirements past due. An empty list opens them again. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.capabilities_blocked/v1",
  "title": "psp.capabilities_blocked",
  "description": "What a partner's past-due requirements set to block close changed (Épico 27, decision 1): the capabilities now closed (sandbox, pilot, live_payments, payouts, remittances), the ones before, and the requirements past due. An empty list opens them again.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "The partner or KulPay meets the past-due requirements; the capabilities open again by themselves."
  },
  "type": "object",
  "required": [
    "psp_id",
    "blocked",
    "previous"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "blocked": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "previous": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "past_due": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "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.exceptions

A certification run passed every mandatory case but some optional ones failed: the PSP is certified only once two people accept the failed optional cases. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.certification.exceptions/v1",
  "title": "psp.certification.exceptions",
  "description": "A certification run passed every mandatory case but some optional ones failed: the PSP is certified only once two people accept the failed optional cases.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "An operator prepares the acceptance of the failed optional cases and a second one approves it, or the PSP repeats them."
  },
  "type": "object",
  "required": [
    "psp_id",
    "run_id",
    "optional_failed"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "run_id": {
      "type": "string"
    },
    "optional_failed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "passed": {
      "type": "integer"
    },
    "report_sha256": {
      "type": "string"
    }
  },
  "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.kyb.approved

KYB was approved: by a second compliance officer at KulPay (maker-checker), or, for an LSP KulOffice onboarded, by KulOffice's own four-eyes approval, which KulPay relies on (KO-1: the same regulated entity). basis says which, decision_ref names KulOffice's decision. The 1-day clock to live starts. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.kyb.approved/v2",
  "title": "psp.kyb.approved",
  "description": "KYB was approved: by a second compliance officer at KulPay (maker-checker), or, for an LSP KulOffice onboarded, by KulOffice's own four-eyes approval, which KulPay relies on (KO-1: the same regulated entity). basis says which, decision_ref names KulOffice's decision. 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"
    },
    "basis": {
      "enum": [
        "kulpay",
        "kuloffice"
      ],
      "description": "Who decided the KYB: KulPay's compliance (absent means kulpay), or KulOffice, relied on."
    },
    "decision_ref": {
      "type": "string",
      "description": "KulOffice's reference for its KYB and approval decision, for the audit trail."
    }
  },
  "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.partner.reminded

The partner was reminded by email of the technical setup steps it has not finished, by an operator or by the system after the steps were left idle. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.partner.reminded/v1",
  "title": "psp.partner.reminded",
  "description": "The partner was reminded by email of the technical setup steps it has not finished, by an operator or by the system after the steps were left idle.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "The partner finishes the steps in the portal."
  },
  "type": "object",
  "required": [
    "psp_id",
    "pending_steps",
    "recipients",
    "automatic"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "pending_steps": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "recipients": {
      "type": "integer"
    },
    "automatic": {
      "type": "boolean"
    },
    "note": {
      "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.screening.subject_changed

A person was added to, or taken off, the list a partner's KYB checks against the sanctions and PEP lists: a director, a beneficial owner or a signatory, from its documents. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.screening.subject_changed/v1",
  "title": "psp.screening.subject_changed",
  "description": "A person was added to, or taken off, the list a partner's KYB checks against the sanctions and PEP lists: a director, a beneficial owner or a signatory, from its documents.",
  "x-kulpay": {
    "aggregate": "psp",
    "webhook": false,
    "terminal": false,
    "act": "Check the person against the sanctions and PEP lists."
  },
  "type": "object",
  "required": [
    "psp_id",
    "subject_id",
    "change"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "subject_id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "role": {
      "type": "string"
    },
    "change": {
      "enum": [
        "added",
        "removed"
      ]
    }
  },
  "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.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.applications_changed v1 no no A connector binding's application allowlist changed (KulOffice phase 6). Empty: any application of the account may use the binding. Audit record. Compare before and after.
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.applications_changed

A connector binding's application allowlist changed (KulOffice phase 6). Empty: any application of the account may use the binding. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.connector.applications_changed/v1",
  "title": "config.connector.applications_changed",
  "description": "A connector binding's application allowlist changed (KulOffice phase 6). Empty: any application of the account may use the binding.",
  "x-kulpay": {
    "aggregate": "connector",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "binding_id",
    "before",
    "after"
  ],
  "properties": {
    "binding_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "mode": {
      "enum": [
        "live",
        "test"
      ]
    },
    "method": {
      "type": "string"
    },
    "before": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "after": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

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
}

custody_account events

EventWebhookTerminalFires whenWhat to do
config.custody_account.added v1 no no An external account (a provider account behind a connector binding, or a bank account whose statements are imported) was registered behind a ledger custody pool (client_funds or provider_custody, per mode and currency) for custody reconciliation (IG-71). None.

config.custody_account.added

An external account (a provider account behind a connector binding, or a bank account whose statements are imported) was registered behind a ledger custody pool (client_funds or provider_custody, per mode and currency) for custody reconciliation (IG-71). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.custody_account.added/v1",
  "title": "config.custody_account.added",
  "description": "An external account (a provider account behind a connector binding, or a bank account whose statements are imported) was registered behind a ledger custody pool (client_funds or provider_custody, per mode and currency) for custody reconciliation (IG-71).",
  "x-kulpay": {
    "aggregate": "custody_account",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "custody_account_id",
    "mode",
    "currency",
    "ledger_code"
  ],
  "properties": {
    "custody_account_id": {
      "type": "string"
    },
    "mode": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "ledger_code": {
      "type": "string"
    },
    "connector_binding_id": {
      "type": "string"
    },
    "statement_account": {
      "type": "string"
    },
    "label": {
      "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
}

merchant_application events

EventWebhookTerminalFires whenWhat to do
config.merchant_application.created v1 no no A merchant application was created (KulOffice phase 6). Its API keys' requests are attributed to it, and connector binding allowlists name it. Audit record.
config.merchant_application.updated v1 no no A merchant application was renamed, disabled or enabled. A disabled application's keys are refused at charge authorization. Audit record. Compare before and after.

config.merchant_application.created

A merchant application was created (KulOffice phase 6). Its API keys' requests are attributed to it, and connector binding allowlists name it. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.merchant_application.created/v1",
  "title": "config.merchant_application.created",
  "description": "A merchant application was created (KulOffice phase 6). Its API keys' requests are attributed to it, and connector binding allowlists name it.",
  "x-kulpay": {
    "aggregate": "merchant_application",
    "webhook": false,
    "terminal": false,
    "act": "Audit record."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "after": {
      "$ref": "kulpay://objects/merchant_application.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

config.merchant_application.updated

A merchant application was renamed, disabled or enabled. A disabled application's keys are refused at charge authorization. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.merchant_application.updated/v1",
  "title": "config.merchant_application.updated",
  "description": "A merchant application was renamed, disabled or enabled. A disabled application's keys are refused at charge authorization.",
  "x-kulpay": {
    "aggregate": "merchant_application",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after."
  },
  "type": "object",
  "required": [
    "before",
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/merchant_application.json"
    },
    "after": {
      "$ref": "kulpay://objects/merchant_application.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "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
}

service_event_sink events

EventWebhookTerminalFires whenWhat to do
config.service_event_sink.created v1 no no An operator created the platform's service event sink for a mode (IG-22): every account's payment events in that mode are delivered to it, signed with the platform secret. Audit record. Give the trusted service the secret returned once on creation.
config.service_event_sink.updated v1 no no An operator changed the service event sink's URL or subscription, or disabled or enabled it (IG-22). Audit record. Compare before and after; replay what a disabled sink missed once it is enabled.
credential.service_event_sink_secret.rotated v1 no no The service event sink's signing secret was rolled (IG-22, as 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.
service_event_sink.replayed v1 no no Deliveries to the service event sink were queued again (IG-22): the dead-lettered ones, or every event since one; each event once. None.

config.service_event_sink.created

An operator created the platform's service event sink for a mode (IG-22): every account's payment events in that mode are delivered to it, signed with the platform secret. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.service_event_sink.created/v1",
  "title": "config.service_event_sink.created",
  "description": "An operator created the platform's service event sink for a mode (IG-22): every account's payment events in that mode are delivered to it, signed with the platform secret.",
  "x-kulpay": {
    "aggregate": "service_event_sink",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Give the trusted service the secret returned once on creation."
  },
  "type": "object",
  "required": [
    "after"
  ],
  "properties": {
    "after": {
      "$ref": "kulpay://objects/service_event_sink.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

config.service_event_sink.updated

An operator changed the service event sink's URL or subscription, or disabled or enabled it (IG-22). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.service_event_sink.updated/v1",
  "title": "config.service_event_sink.updated",
  "description": "An operator changed the service event sink's URL or subscription, or disabled or enabled it (IG-22).",
  "x-kulpay": {
    "aggregate": "service_event_sink",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Compare before and after; replay what a disabled sink missed once it is enabled."
  },
  "type": "object",
  "required": [
    "before",
    "after"
  ],
  "properties": {
    "before": {
      "$ref": "kulpay://objects/service_event_sink.json"
    },
    "after": {
      "$ref": "kulpay://objects/service_event_sink.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

credential.service_event_sink_secret.rotated

The service event sink's signing secret was rolled (IG-22, as 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.service_event_sink_secret.rotated/v1",
  "title": "credential.service_event_sink_secret.rotated",
  "description": "The service event sink's signing secret was rolled (IG-22, as 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": "service_event_sink",
    "webhook": false,
    "terminal": false,
    "act": "Deploy the new secret to the receiver before previous_expires_at."
  },
  "type": "object",
  "required": [
    "sink_id",
    "previous_expires_at",
    "grace_seconds"
  ],
  "properties": {
    "sink_id": {
      "type": "string"
    },
    "previous_expires_at": {
      "type": "string",
      "format": "date-time"
    },
    "grace_seconds": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

service_event_sink.replayed

Deliveries to the service event sink were queued again (IG-22): 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/service_event_sink.replayed/v1",
  "title": "service_event_sink.replayed",
  "description": "Deliveries to the service event sink were queued again (IG-22): the dead-lettered ones, or every event since one; each event once.",
  "x-kulpay": {
    "aggregate": "service_event_sink",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "sink_id",
    "count"
  ],
  "properties": {
    "sink_id": {
      "type": "string"
    },
    "count": {
      "type": "integer"
    },
    "since_event_id": {
      "type": "string"
    },
    "status": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

settlement_arrangement events

EventWebhookTerminalFires whenWhat to do
config.settlement_arrangement.activated v1 no no A settlement arrangement version was approved and is in force for the merchant's new payments in its currency (plan phase 8), superseding the previous version. Each payment records the version it was routed under, so payments made before keep their terms. None: new payments in this currency follow this version.
config.settlement_arrangement.version_created v1 no no A version of a merchant's settlement arrangement in a currency was proposed (plan phase 8): custody, destination reference, schedule, threshold, reserve, merchant-borne deductions and refund-offset policy. It waits for a second person; nothing changes until it is approved. A different operator approves or rejects it (POST /v1/admin/approvals/{id}/decide).

config.settlement_arrangement.activated

A settlement arrangement version was approved and is in force for the merchant's new payments in its currency (plan phase 8), superseding the previous version. Each payment records the version it was routed under, so payments made before keep their terms. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.settlement_arrangement.activated/v1",
  "title": "config.settlement_arrangement.activated",
  "description": "A settlement arrangement version was approved and is in force for the merchant's new payments in its currency (plan phase 8), superseding the previous version. Each payment records the version it was routed under, so payments made before keep their terms.",
  "x-kulpay": {
    "aggregate": "settlement_arrangement",
    "webhook": false,
    "terminal": false,
    "act": "None: new payments in this currency follow this version."
  },
  "type": "object",
  "required": [
    "arrangement_id",
    "account_id",
    "currency",
    "version",
    "custody",
    "previous_version"
  ],
  "properties": {
    "arrangement_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "custody": {
      "type": "string",
      "enum": [
        "managed_pass_through",
        "merchant_direct"
      ]
    },
    "previous_version": {
      "type": "integer"
    },
    "approved_by": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

config.settlement_arrangement.version_created

A version of a merchant's settlement arrangement in a currency was proposed (plan phase 8): custody, destination reference, schedule, threshold, reserve, merchant-borne deductions and refund-offset policy. It waits for a second person; nothing changes until it is approved. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.settlement_arrangement.version_created/v1",
  "title": "config.settlement_arrangement.version_created",
  "description": "A version of a merchant's settlement arrangement in a currency was proposed (plan phase 8): custody, destination reference, schedule, threshold, reserve, merchant-borne deductions and refund-offset policy. It waits for a second person; nothing changes until it is approved.",
  "x-kulpay": {
    "aggregate": "settlement_arrangement",
    "webhook": false,
    "terminal": false,
    "act": "A different operator approves or rejects it (POST /v1/admin/approvals/{id}/decide)."
  },
  "type": "object",
  "required": [
    "arrangement_id",
    "account_id",
    "currency",
    "version",
    "custody"
  ],
  "properties": {
    "arrangement_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "version": {
      "type": "integer"
    },
    "custody": {
      "type": "string",
      "enum": [
        "managed_pass_through",
        "merchant_direct"
      ]
    },
    "reason": {
      "type": "string"
    },
    "created_by": {
      "type": "string"
    },
    "approval_id": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

webhook_endpoint events

EventWebhookTerminalFires whenWhat to do
config.webhook_endpoint.registered v2 no no A webhook endpoint was registered (v2: with the merchant application it is scoped to). Audit record. Compare before and after.
config.webhook_endpoint.removed v2 no no A webhook endpoint was removed (v2: with the merchant application it is scoped to). 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.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 (v2: with the merchant application it is scoped to). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.webhook_endpoint.registered/v2",
  "title": "config.webhook_endpoint.registered",
  "description": "A webhook endpoint was registered (v2: with the merchant application it is scoped to).",
  "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.v2.json"
    },
    "after": {
      "$ref": "kulpay://objects/webhook_endpoint.v2.json"
    },
    "changed": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}

config.webhook_endpoint.removed

A webhook endpoint was removed (v2: with the merchant application it is scoped to). Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/config.webhook_endpoint.removed/v2",
  "title": "config.webhook_endpoint.removed",
  "description": "A webhook endpoint was removed (v2: with the merchant application it is scoped to).",
  "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.v2.json"
    },
    "after": {
      "$ref": "kulpay://objects/webhook_endpoint.v2.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.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
}

connector_session events

EventWebhookTerminalFires whenWhat to do
credential.connector_session.completed v1 no no Credentials were submitted through a secure configuration session and vaulted on the binding (envelope-encrypted). Only the field names are recorded. Audit record. The binding's own config.connector.configured event follows.
credential.connector_session.created v1 no no A secure connector-configuration session was opened (KulOffice phase 6). The merchant submits its provider credentials to the Gateway-hosted page it names; nothing secret is recorded. Audit record. Check who opened it.
credential.connector_session.refused v1 no no A submission to a secure configuration session was refused: the session was already used (a replay), had expired, or the credentials did not build a connector. Nothing was stored. A replay of a used session may mean its link leaked: open a new session and check who had it.

credential.connector_session.completed

Credentials were submitted through a secure configuration session and vaulted on the binding (envelope-encrypted). Only the field names are recorded. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.connector_session.completed/v1",
  "title": "credential.connector_session.completed",
  "description": "Credentials were submitted through a secure configuration session and vaulted on the binding (envelope-encrypted). Only the field names are recorded.",
  "x-kulpay": {
    "aggregate": "connector_session",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. The binding's own config.connector.configured event follows."
  },
  "type": "object",
  "required": [
    "session"
  ],
  "properties": {
    "session": {
      "$ref": "kulpay://objects/connector_session.json"
    }
  },
  "additionalProperties": false
}

credential.connector_session.created

A secure connector-configuration session was opened (KulOffice phase 6). The merchant submits its provider credentials to the Gateway-hosted page it names; nothing secret is recorded. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.connector_session.created/v1",
  "title": "credential.connector_session.created",
  "description": "A secure connector-configuration session was opened (KulOffice phase 6). The merchant submits its provider credentials to the Gateway-hosted page it names; nothing secret is recorded.",
  "x-kulpay": {
    "aggregate": "connector_session",
    "webhook": false,
    "terminal": false,
    "act": "Audit record. Check who opened it."
  },
  "type": "object",
  "required": [
    "session"
  ],
  "properties": {
    "session": {
      "$ref": "kulpay://objects/connector_session.json"
    }
  },
  "additionalProperties": false
}

credential.connector_session.refused

A submission to a secure configuration session was refused: the session was already used (a replay), had expired, or the credentials did not build a connector. Nothing was stored. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/credential.connector_session.refused/v1",
  "title": "credential.connector_session.refused",
  "description": "A submission to a secure configuration session was refused: the session was already used (a replay), had expired, or the credentials did not build a connector. Nothing was stored.",
  "x-kulpay": {
    "aggregate": "connector_session",
    "webhook": false,
    "terminal": false,
    "act": "A replay of a used session may mean its link leaked: open a new session and check who had it."
  },
  "type": "object",
  "required": [
    "session",
    "reason"
  ],
  "properties": {
    "session": {
      "$ref": "kulpay://objects/connector_session.json"
    },
    "reason": {
      "enum": [
        "already_used",
        "expired",
        "invalid_configuration"
      ]
    }
  },
  "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
}

custody_reconciliation events

EventWebhookTerminalFires whenWhat to do
custody.reconciliation.recorded v1 no no A ledger custody pool (mode, currency, client_funds or provider_custody, across tenants) was compared with the balances its banks or providers report as of a cutoff (IG-71): matched, difference (a custody_difference exception is opened) or unverifiable (no balance read and no statement). Nothing moves. On a difference, work the custody_difference exception; on unverifiable, register a statement account or a balance-reading binding.

custody.reconciliation.recorded

A ledger custody pool (mode, currency, client_funds or provider_custody, across tenants) was compared with the balances its banks or providers report as of a cutoff (IG-71): matched, difference (a custody_difference exception is opened) or unverifiable (no balance read and no statement). Nothing moves. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/custody.reconciliation.recorded/v1",
  "title": "custody.reconciliation.recorded",
  "description": "A ledger custody pool (mode, currency, client_funds or provider_custody, across tenants) was compared with the balances its banks or providers report as of a cutoff (IG-71): matched, difference (a custody_difference exception is opened) or unverifiable (no balance read and no statement). Nothing moves.",
  "x-kulpay": {
    "aggregate": "custody_reconciliation",
    "webhook": false,
    "terminal": false,
    "act": "On a difference, work the custody_difference exception; on unverifiable, register a statement account or a balance-reading binding."
  },
  "type": "object",
  "required": [
    "reconciliation_id",
    "mode",
    "currency",
    "ledger_code",
    "cutoff",
    "status",
    "ledger_minor",
    "external_minor",
    "difference_minor"
  ],
  "properties": {
    "reconciliation_id": {
      "type": "string"
    },
    "mode": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "ledger_code": {
      "type": "string"
    },
    "cutoff": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "matched",
        "difference",
        "unverifiable"
      ]
    },
    "ledger_minor": {
      "type": "integer"
    },
    "external_minor": {
      "type": "integer"
    },
    "difference_minor": {
      "type": "integer"
    },
    "exception_id": {
      "type": "string"
    },
    "reason": {
      "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
}

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
}

incoming_transfer events

EventWebhookTerminalFires whenWhat to do
incoming_transfer.compliance_review v1 yes no An operator sent the transfer to compliance review: it must not be credited until it is released, bound or returned. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Hold any credit; wait for the outcome.
incoming_transfer.confirmed_final v1 yes no The transfer is matched and final: the only state in which it may be credited. Read it back through the API before crediting. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Read the transfer, credit the beneficiary once, then POST /v1/incoming-transfers/{id}/credit.
incoming_transfer.credited v1 yes yes The beneficiary reported crediting the transfer (credited_notified), under its own credit reference. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Nothing; the transfer is complete.
incoming_transfer.finality_confirmed v1 yes no The partner confirmed the transfer final while it had no beneficiary to credit (unmatched, or in compliance review): the money is in suspense. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Nothing to credit; an operator resolves it.
incoming_transfer.matched v1 yes no The transfer was matched to its beneficiary (by deposit reference, alias or virtual account, or bound by an operator) but is not final yet: it must not be credited. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Prepare the credit; do not apply it until incoming_transfer.confirmed_final.
incoming_transfer.received v1 yes no A certified partner (account_funding.incoming) reported an incoming transfer, kept with its partner_reference and the facts first reported; its beneficiary is being resolved. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Nothing to credit yet: wait for matched, unmatched or confirmed_final.
incoming_transfer.returned v1 yes yes With a second person's approval, the transfer is to be returned to its source; it will never be credited. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Partner: return the funds to the originator. Beneficiary: cancel anything prepared.
incoming_transfer.unmatched v1 yes no No beneficiary was found for the transfer. It stays in the gateway's suspense (once final) until an operator binds it, returns it to its source or sends it to compliance review. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Partner: nothing to do unless asked; the transfer will be bound or returned.

incoming_transfer.compliance_review

An operator sent the transfer to compliance review: it must not be credited until it is released, bound or returned. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/incoming_transfer.compliance_review/v1",
  "title": "incoming_transfer.compliance_review",
  "description": "An operator sent the transfer to compliance review: it must not be credited until it is released, bound or returned. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version.",
  "x-kulpay": {
    "aggregate": "incoming_transfer",
    "webhook": true,
    "terminal": false,
    "act": "Hold any credit; wait for the outcome."
  },
  "type": "object",
  "required": [
    "incoming_transfer_id",
    "partner_reference",
    "status",
    "mode",
    "amount_minor",
    "currency",
    "final",
    "version"
  ],
  "properties": {
    "incoming_transfer_id": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string",
      "description": "The reporting partner's own transfer reference."
    },
    "status": {
      "type": "string",
      "enum": [
        "compliance_review"
      ]
    },
    "mode": {
      "type": "string",
      "enum": [
        "test",
        "live"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "minimum": 1
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$"
    },
    "final": {
      "type": "boolean"
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "The transfer's version: a later event has a higher one."
    },
    "beneficiary_account_id": {
      "type": "string",
      "description": "Only in the beneficiary's copy, once matched."
    }
  },
  "additionalProperties": false
}

incoming_transfer.confirmed_final

The transfer is matched and final: the only state in which it may be credited. Read it back through the API before crediting. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/incoming_transfer.confirmed_final/v1",
  "title": "incoming_transfer.confirmed_final",
  "description": "The transfer is matched and final: the only state in which it may be credited. Read it back through the API before crediting. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version.",
  "x-kulpay": {
    "aggregate": "incoming_transfer",
    "webhook": true,
    "terminal": false,
    "act": "Read the transfer, credit the beneficiary once, then POST /v1/incoming-transfers/{id}/credit."
  },
  "type": "object",
  "required": [
    "incoming_transfer_id",
    "partner_reference",
    "status",
    "mode",
    "amount_minor",
    "currency",
    "final",
    "version"
  ],
  "properties": {
    "incoming_transfer_id": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string",
      "description": "The reporting partner's own transfer reference."
    },
    "status": {
      "type": "string",
      "enum": [
        "confirmed_final"
      ]
    },
    "mode": {
      "type": "string",
      "enum": [
        "test",
        "live"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "minimum": 1
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$"
    },
    "final": {
      "const": true
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "The transfer's version: a later event has a higher one."
    },
    "beneficiary_account_id": {
      "type": "string",
      "description": "Only in the beneficiary's copy, once matched."
    }
  },
  "additionalProperties": false
}

incoming_transfer.credited

The beneficiary reported crediting the transfer (credited_notified), under its own credit reference. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/incoming_transfer.credited/v1",
  "title": "incoming_transfer.credited",
  "description": "The beneficiary reported crediting the transfer (credited_notified), under its own credit reference. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version.",
  "x-kulpay": {
    "aggregate": "incoming_transfer",
    "webhook": true,
    "terminal": true,
    "act": "Nothing; the transfer is complete."
  },
  "type": "object",
  "required": [
    "incoming_transfer_id",
    "partner_reference",
    "status",
    "mode",
    "amount_minor",
    "currency",
    "final",
    "version"
  ],
  "properties": {
    "incoming_transfer_id": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string",
      "description": "The reporting partner's own transfer reference."
    },
    "status": {
      "type": "string",
      "enum": [
        "credited_notified"
      ]
    },
    "mode": {
      "type": "string",
      "enum": [
        "test",
        "live"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "minimum": 1
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$"
    },
    "final": {
      "type": "boolean"
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "The transfer's version: a later event has a higher one."
    },
    "beneficiary_account_id": {
      "type": "string",
      "description": "Only in the beneficiary's copy, once matched."
    }
  },
  "additionalProperties": false
}

incoming_transfer.finality_confirmed

The partner confirmed the transfer final while it had no beneficiary to credit (unmatched, or in compliance review): the money is in suspense. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/incoming_transfer.finality_confirmed/v1",
  "title": "incoming_transfer.finality_confirmed",
  "description": "The partner confirmed the transfer final while it had no beneficiary to credit (unmatched, or in compliance review): the money is in suspense. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version.",
  "x-kulpay": {
    "aggregate": "incoming_transfer",
    "webhook": true,
    "terminal": false,
    "act": "Nothing to credit; an operator resolves it."
  },
  "type": "object",
  "required": [
    "incoming_transfer_id",
    "partner_reference",
    "status",
    "mode",
    "amount_minor",
    "currency",
    "final",
    "version"
  ],
  "properties": {
    "incoming_transfer_id": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string",
      "description": "The reporting partner's own transfer reference."
    },
    "status": {
      "type": "string",
      "enum": [
        "received",
        "unmatched",
        "compliance_review"
      ]
    },
    "mode": {
      "type": "string",
      "enum": [
        "test",
        "live"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "minimum": 1
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$"
    },
    "final": {
      "type": "boolean"
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "The transfer's version: a later event has a higher one."
    },
    "beneficiary_account_id": {
      "type": "string",
      "description": "Only in the beneficiary's copy, once matched."
    }
  },
  "additionalProperties": false
}

incoming_transfer.matched

The transfer was matched to its beneficiary (by deposit reference, alias or virtual account, or bound by an operator) but is not final yet: it must not be credited. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/incoming_transfer.matched/v1",
  "title": "incoming_transfer.matched",
  "description": "The transfer was matched to its beneficiary (by deposit reference, alias or virtual account, or bound by an operator) but is not final yet: it must not be credited. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version.",
  "x-kulpay": {
    "aggregate": "incoming_transfer",
    "webhook": true,
    "terminal": false,
    "act": "Prepare the credit; do not apply it until incoming_transfer.confirmed_final."
  },
  "type": "object",
  "required": [
    "incoming_transfer_id",
    "partner_reference",
    "status",
    "mode",
    "amount_minor",
    "currency",
    "final",
    "version"
  ],
  "properties": {
    "incoming_transfer_id": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string",
      "description": "The reporting partner's own transfer reference."
    },
    "status": {
      "type": "string",
      "enum": [
        "matched"
      ]
    },
    "mode": {
      "type": "string",
      "enum": [
        "test",
        "live"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "minimum": 1
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$"
    },
    "final": {
      "type": "boolean"
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "The transfer's version: a later event has a higher one."
    },
    "beneficiary_account_id": {
      "type": "string",
      "description": "Only in the beneficiary's copy, once matched."
    }
  },
  "additionalProperties": false
}

incoming_transfer.received

A certified partner (account_funding.incoming) reported an incoming transfer, kept with its partner_reference and the facts first reported; its beneficiary is being resolved. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/incoming_transfer.received/v1",
  "title": "incoming_transfer.received",
  "description": "A certified partner (account_funding.incoming) reported an incoming transfer, kept with its partner_reference and the facts first reported; its beneficiary is being resolved. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version.",
  "x-kulpay": {
    "aggregate": "incoming_transfer",
    "webhook": true,
    "terminal": false,
    "act": "Nothing to credit yet: wait for matched, unmatched or confirmed_final."
  },
  "type": "object",
  "required": [
    "incoming_transfer_id",
    "partner_reference",
    "status",
    "mode",
    "amount_minor",
    "currency",
    "final",
    "version"
  ],
  "properties": {
    "incoming_transfer_id": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string",
      "description": "The reporting partner's own transfer reference."
    },
    "status": {
      "type": "string",
      "enum": [
        "received"
      ]
    },
    "mode": {
      "type": "string",
      "enum": [
        "test",
        "live"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "minimum": 1
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$"
    },
    "final": {
      "type": "boolean"
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "The transfer's version: a later event has a higher one."
    }
  },
  "additionalProperties": false
}

incoming_transfer.returned

With a second person's approval, the transfer is to be returned to its source; it will never be credited. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/incoming_transfer.returned/v1",
  "title": "incoming_transfer.returned",
  "description": "With a second person's approval, the transfer is to be returned to its source; it will never be credited. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version.",
  "x-kulpay": {
    "aggregate": "incoming_transfer",
    "webhook": true,
    "terminal": true,
    "act": "Partner: return the funds to the originator. Beneficiary: cancel anything prepared."
  },
  "type": "object",
  "required": [
    "incoming_transfer_id",
    "partner_reference",
    "status",
    "mode",
    "amount_minor",
    "currency",
    "final",
    "version"
  ],
  "properties": {
    "incoming_transfer_id": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string",
      "description": "The reporting partner's own transfer reference."
    },
    "status": {
      "type": "string",
      "enum": [
        "returned"
      ]
    },
    "mode": {
      "type": "string",
      "enum": [
        "test",
        "live"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "minimum": 1
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$"
    },
    "final": {
      "type": "boolean"
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "The transfer's version: a later event has a higher one."
    },
    "beneficiary_account_id": {
      "type": "string",
      "description": "Only in the beneficiary's copy, once matched."
    }
  },
  "additionalProperties": false
}

incoming_transfer.unmatched

No beneficiary was found for the transfer. It stays in the gateway's suspense (once final) until an operator binds it, returns it to its source or sends it to compliance review. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/incoming_transfer.unmatched/v1",
  "title": "incoming_transfer.unmatched",
  "description": "No beneficiary was found for the transfer. It stays in the gateway's suspense (once final) until an operator binds it, returns it to its source or sends it to compliance review. Carries identifiers, amount and status only: read the transfer with GET /v1/incoming-transfers/{id}. The beneficiary's copy names beneficiary_account_id; the partner's never does. Order by version.",
  "x-kulpay": {
    "aggregate": "incoming_transfer",
    "webhook": true,
    "terminal": false,
    "act": "Partner: nothing to do unless asked; the transfer will be bound or returned."
  },
  "type": "object",
  "required": [
    "incoming_transfer_id",
    "partner_reference",
    "status",
    "mode",
    "amount_minor",
    "currency",
    "final",
    "version"
  ],
  "properties": {
    "incoming_transfer_id": {
      "type": "string"
    },
    "partner_reference": {
      "type": "string",
      "description": "The reporting partner's own transfer reference."
    },
    "status": {
      "type": "string",
      "enum": [
        "unmatched"
      ]
    },
    "mode": {
      "type": "string",
      "enum": [
        "test",
        "live"
      ]
    },
    "amount_minor": {
      "type": "integer",
      "minimum": 1
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$"
    },
    "final": {
      "type": "boolean"
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "The transfer's version: a later event has a higher one."
    },
    "beneficiary_account_id": {
      "type": "string",
      "description": "Only in the beneficiary's copy, once matched."
    }
  },
  "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 v3 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: a bill reference (BIL-5.11) resolves at KulPay: status ambiguous with options, destination_kind, the biller, and why none was found. v3 (BIL-5.14): a reference that names no biller is routed to the billers whose routable products claim it: routed_billers (asked) and skipped (failed or late); biller_required when more billers claim it than one lookup asks (candidates_count). None.
name_lookup.option_selected v1 no no The partner chose which of an ambiguous bill reference's options the deposit pays (BIL-5.11). None.

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: a bill reference (BIL-5.11) resolves at KulPay: status ambiguous with options, destination_kind, the biller, and why none was found. v3 (BIL-5.14): a reference that names no biller is routed to the billers whose routable products claim it: routed_billers (asked) and skipped (failed or late); biller_required when more billers claim it than one lookup asks (candidates_count). Schema (JSON)

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/name_lookup.completed/v3",
  "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: a bill reference (BIL-5.11) resolves at KulPay: status ambiguous with options, destination_kind, the biller, and why none was found. v3 (BIL-5.14): a reference that names no biller is routed to the billers whose routable products claim it: routed_billers (asked) and skipped (failed or late); biller_required when more billers claim it than one lookup asks (candidates_count).",
  "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",
        "biller_required"
      ]
    },
    "masked_name": {
      "type": "string"
    },
    "masked_value": {
      "type": "string"
    },
    "raw_code": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "options_count": {
      "type": "integer"
    },
    "biller": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "routed_billers": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "skipped": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "biller": {
            "type": "string"
          },
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    },
    "candidates_count": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

name_lookup.option_selected

The partner chose which of an ambiguous bill reference's options the deposit pays (BIL-5.11). 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 partner chose which of an ambiguous bill reference's options the deposit pays (BIL-5.11).",
  "x-kulpay": {
    "aggregate": "name_lookup",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "lookup_id",
    "option_id",
    "destination_kind"
  ],
  "properties": {
    "lookup_id": {
      "type": "string"
    },
    "option_id": {
      "type": "string"
    },
    "destination_kind": {
      "enum": [
        "wallet",
        "kulpay_invoice",
        "kulpay_bill"
      ]
    },
    "biller": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

merchant_payable events

EventWebhookTerminalFires whenWhat to do
payable.fee_adjustment_accepted v1 no no A KulOffice fee adjustment of an assessment in a payment's accepted fee instruction was accepted (IG-29). amount_minor is signed: positive raises the fee, negative lowers it. effect platform_fee: it changed the platform-fee deduction of a payable not yet batched. effect carried: the payable was already batched, settled or reversed, so it becomes a fee_adjustment line on the next batched payable of the merchant. effect record_only: kept as evidence (the assessment is not a merchant settlement deduction, or the instruction deducted nothing). None: read the payable (GET /v1/service/payables) for the adjustment line.
payable.fee_instruction_accepted v1 no no KulOffice's complete fee instruction for a managed pass-through payment was accepted (IG-29). effect deducted: the sum of its merchant-borne settlement_deduction lines became the payable's platform_fee deduction (deduction_minor, 0 for an explicit zero fee) and the payable may now be promoted to available. effect record_only: the payable was reversed before the instruction, which is kept as evidence and deducts nothing. Every line is stored as evidence. None: read the payable (GET /v1/service/payables) for its platform_fee lines and their assessment ids.
payable.fee_instruction_overdue v1 no no A managed pass-through payable has waited longer than the configured delay (24 hours by default) for KulOffice's fee instruction (IG-29). It stays provisional and is never batched until a complete instruction is accepted; the Gateway never assumes a fee. Emitted once per payable, to the platform service event sink only (never to a merchant endpoint), for reconciliation and action-required. KulOffice: capture and recognize the payment's fees and PUT /v1/service/payments/{provider_payment_id}/fee-instruction; otherwise open an action-required reconciliation item.
settlement.payable.state_changed v1 no no A managed pass-through merchant payable changed state (plan phase 8): created provisional when the payment succeeded (net = gross - merchant-borne platform fee - provider cost - reserve, in minor units), available after the arrangement's delay, reserved_for_settlement in a ready batch, settled when the batch's external settlement is confirmed, disputed while its payment is disputed, reversed when fully refunded before settlement. Settlement state is independent of the payment's status. None: read the payable (GET /v1/payables/{id}) for its lines and adjustments.

payable.fee_adjustment_accepted

A KulOffice fee adjustment of an assessment in a payment's accepted fee instruction was accepted (IG-29). amount_minor is signed: positive raises the fee, negative lowers it. effect platform_fee: it changed the platform-fee deduction of a payable not yet batched. effect carried: the payable was already batched, settled or reversed, so it becomes a fee_adjustment line on the next batched payable of the merchant. effect record_only: kept as evidence (the assessment is not a merchant settlement deduction, or the instruction deducted nothing). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payable.fee_adjustment_accepted/v1",
  "title": "payable.fee_adjustment_accepted",
  "description": "A KulOffice fee adjustment of an assessment in a payment's accepted fee instruction was accepted (IG-29). amount_minor is signed: positive raises the fee, negative lowers it. effect platform_fee: it changed the platform-fee deduction of a payable not yet batched. effect carried: the payable was already batched, settled or reversed, so it becomes a fee_adjustment line on the next batched payable of the merchant. effect record_only: kept as evidence (the assessment is not a merchant settlement deduction, or the instruction deducted nothing).",
  "x-kulpay": {
    "aggregate": "merchant_payable",
    "webhook": false,
    "terminal": false,
    "act": "None: read the payable (GET /v1/service/payables) for the adjustment line."
  },
  "type": "object",
  "required": [
    "payable_id",
    "provider_payment_id",
    "account_id",
    "currency",
    "adjustment_id",
    "instruction_id",
    "assessment_id",
    "amount_minor",
    "effect"
  ],
  "properties": {
    "payable_id": {
      "type": "string"
    },
    "provider_payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "adjustment_id": {
      "type": "string"
    },
    "instruction_id": {
      "type": "string"
    },
    "assessment_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "effect": {
      "type": "string",
      "enum": [
        "platform_fee",
        "carried",
        "record_only"
      ]
    }
  },
  "additionalProperties": false
}

payable.fee_instruction_accepted

KulOffice's complete fee instruction for a managed pass-through payment was accepted (IG-29). effect deducted: the sum of its merchant-borne settlement_deduction lines became the payable's platform_fee deduction (deduction_minor, 0 for an explicit zero fee) and the payable may now be promoted to available. effect record_only: the payable was reversed before the instruction, which is kept as evidence and deducts nothing. Every line is stored as evidence. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payable.fee_instruction_accepted/v1",
  "title": "payable.fee_instruction_accepted",
  "description": "KulOffice's complete fee instruction for a managed pass-through payment was accepted (IG-29). effect deducted: the sum of its merchant-borne settlement_deduction lines became the payable's platform_fee deduction (deduction_minor, 0 for an explicit zero fee) and the payable may now be promoted to available. effect record_only: the payable was reversed before the instruction, which is kept as evidence and deducts nothing. Every line is stored as evidence.",
  "x-kulpay": {
    "aggregate": "merchant_payable",
    "webhook": false,
    "terminal": false,
    "act": "None: read the payable (GET /v1/service/payables) for its platform_fee lines and their assessment ids."
  },
  "type": "object",
  "required": [
    "payable_id",
    "provider_payment_id",
    "account_id",
    "currency",
    "instruction_id",
    "effect",
    "deduction_minor",
    "line_count"
  ],
  "properties": {
    "payable_id": {
      "type": "string"
    },
    "provider_payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "instruction_id": {
      "type": "string"
    },
    "effect": {
      "type": "string",
      "enum": [
        "deducted",
        "record_only"
      ]
    },
    "deduction_minor": {
      "type": "integer"
    },
    "line_count": {
      "type": "integer"
    },
    "net_minor": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

payable.fee_instruction_overdue

A managed pass-through payable has waited longer than the configured delay (24 hours by default) for KulOffice's fee instruction (IG-29). It stays provisional and is never batched until a complete instruction is accepted; the Gateway never assumes a fee. Emitted once per payable, to the platform service event sink only (never to a merchant endpoint), for reconciliation and action-required. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payable.fee_instruction_overdue/v1",
  "title": "payable.fee_instruction_overdue",
  "description": "A managed pass-through payable has waited longer than the configured delay (24 hours by default) for KulOffice's fee instruction (IG-29). It stays provisional and is never batched until a complete instruction is accepted; the Gateway never assumes a fee. Emitted once per payable, to the platform service event sink only (never to a merchant endpoint), for reconciliation and action-required.",
  "x-kulpay": {
    "aggregate": "merchant_payable",
    "webhook": false,
    "terminal": false,
    "act": "KulOffice: capture and recognize the payment's fees and PUT /v1/service/payments/{provider_payment_id}/fee-instruction; otherwise open an action-required reconciliation item."
  },
  "type": "object",
  "required": [
    "payable_id",
    "provider_payment_id",
    "account_id",
    "currency",
    "gross_minor",
    "state",
    "awaiting_since",
    "overdue_after_seconds"
  ],
  "properties": {
    "payable_id": {
      "type": "string"
    },
    "provider_payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "gross_minor": {
      "type": "integer"
    },
    "state": {
      "type": "string",
      "enum": [
        "provisional",
        "disputed"
      ]
    },
    "awaiting_since": {
      "type": "string",
      "format": "date-time"
    },
    "overdue_after_seconds": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

settlement.payable.state_changed

A managed pass-through merchant payable changed state (plan phase 8): created provisional when the payment succeeded (net = gross - merchant-borne platform fee - provider cost - reserve, in minor units), available after the arrangement's delay, reserved_for_settlement in a ready batch, settled when the batch's external settlement is confirmed, disputed while its payment is disputed, reversed when fully refunded before settlement. Settlement state is independent of the payment's status. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/settlement.payable.state_changed/v1",
  "title": "settlement.payable.state_changed",
  "description": "A managed pass-through merchant payable changed state (plan phase 8): created provisional when the payment succeeded (net = gross - merchant-borne platform fee - provider cost - reserve, in minor units), available after the arrangement's delay, reserved_for_settlement in a ready batch, settled when the batch's external settlement is confirmed, disputed while its payment is disputed, reversed when fully refunded before settlement. Settlement state is independent of the payment's status.",
  "x-kulpay": {
    "aggregate": "merchant_payable",
    "webhook": false,
    "terminal": false,
    "act": "None: read the payable (GET /v1/payables/{id}) for its lines and adjustments."
  },
  "type": "object",
  "required": [
    "payable_id",
    "payment_id",
    "account_id",
    "currency",
    "from",
    "to",
    "net_minor"
  ],
  "properties": {
    "payable_id": {
      "type": "string"
    },
    "payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "from": {
      "type": "string",
      "enum": [
        "",
        "provisional",
        "available",
        "reserved_for_settlement",
        "settled",
        "disputed",
        "reversed"
      ]
    },
    "to": {
      "type": "string",
      "enum": [
        "provisional",
        "available",
        "reserved_for_settlement",
        "settled",
        "disputed",
        "reversed"
      ]
    },
    "net_minor": {
      "type": "integer"
    },
    "batch_id": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

payment events

EventWebhookTerminalFires whenWhat to do
payment.authorized v2 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 v3 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 v3 no no A capture was claimed and sent to the provider. Progress only.
payment.created v3 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 v3 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 v3 yes yes The customer never completed the payment inside its window. Same as cancelled: release, no money moved.
payment.failed v3 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 v3 no no The customer chose a payment method on the hosted checkout. Progress only.
payment.partially_refunded v3 no no Part of the payment was refunded. Informational; act on refund.succeeded.
payment.pending v3 no no The payment is waiting on the provider or the customer. Progress only. Wait for a terminal event.
payment.processing v3 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 v3 no no The payment was fully refunded. Informational; act on refund.succeeded.
payment.requires_action v3 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 v3 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 v3 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 v3 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 v1 no no A payment's PSP result is unknown: the answer never came back after sending (timeout, dropped connection), or it stayed in flight past its window. 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 v3 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, no PSP is available, the connector binding that would take it is restricted to other merchant applications (application_not_allowed, phase 6), or the launch capability matrix does not list the route (route_not_enabled) or its movement scope is disabled (movement_scope_disabled, off_net; plan phase 10). No payment was created unless the request belonged to a checkout. None for the platform; the caller got 422 (not_supported, limit_exceeded, route_not_enabled, movement_scope_disabled), 503 (no_route) or 403 (application_not_allowed). Frequent refusals for one rule may mean a capability matrix, routing rule, binding allowlist or the launch matrix 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 v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.authorized/v2",
  "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.v3.json"
}

payment.cancelled

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.cancelled/v3",
  "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.v3.json"
}

payment.capture_requested

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.capture_requested/v3",
  "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.v3.json"
}

payment.created

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.created/v3",
  "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.v3.json"
}

payment.disputed

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.disputed/v3",
  "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.v3.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 v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.expired/v3",
  "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.v3.json"
}

payment.failed

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.failed/v3",
  "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.v3.json"
}

payment.method_selected

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.method_selected/v3",
  "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.v3.json"
}

payment.partially_refunded

Part of the payment was refunded. Schema (JSON)

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.partially_refunded/v3",
  "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.v3.json"
}

payment.pending

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.pending/v3",
  "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.v3.json"
}

payment.processing

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.processing/v3",
  "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.v3.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 v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.refunded/v3",
  "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.v3.json"
}

payment.requires_action

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.requires_action/v3",
  "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.v3.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 v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.succeeded/v3",
  "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.v3.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 v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.updated/v3",
  "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.v3.json"
}

payment.void_requested

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

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.void_requested/v3",
  "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.v3.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), or it stayed in flight past its window. It stays processing while status queries find out what happened. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.outcome.unknown/v1",
  "title": "transaction.outcome.unknown",
  "description": "A payment's PSP result is unknown: the answer never came back after sending (timeout, dropped connection), or it stayed in flight past its window. 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"
      ]
    },
    "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, no PSP is available, the connector binding that would take it is restricted to other merchant applications (application_not_allowed, phase 6), or the launch capability matrix does not list the route (route_not_enabled) or its movement scope is disabled (movement_scope_disabled, off_net; plan phase 10). No payment was created unless the request belonged to a checkout. Schema (JSON)

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/transaction.rejected/v3",
  "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, no PSP is available, the connector binding that would take it is restricted to other merchant applications (application_not_allowed, phase 6), or the launch capability matrix does not list the route (route_not_enabled) or its movement scope is disabled (movement_scope_disabled, off_net; plan phase 10). 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, route_not_enabled, movement_scope_disabled), 503 (no_route) or 403 (application_not_allowed). Frequent refusals for one rule may mean a capability matrix, routing rule, binding allowlist or the launch matrix 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",
        "application_not_allowed",
        "route_not_enabled",
        "movement_scope_disabled"
      ]
    },
    "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
}

payment_fee_instruction events

EventWebhookTerminalFires whenWhat to do
payment.fee_adjustment_accepted v1 no no A KulOffice fee adjustment of an assessment in a collecting payment's accepted fee instruction was accepted (IG-80). amount_minor is signed: positive raises the fee, negative lowers it. effect platform_fee: the payment was not batched yet, the fee change joins its batch. effect carried: the payment was already batched, so the fee change is a movement of the next collecting batch. effect record_only: kept as evidence (the assessment is not a merchant settlement deduction). The aggregate id is the provider payment id. None: the fee entry is in the payment's next batch line (GET /v1/service/settlements/{batch_id}).
payment.fee_instruction_accepted v1 no no KulOffice's complete fee instruction for a collecting payment was accepted (IG-80). The sum of its merchant-borne settlement_deduction lines (deduction_minor, 0 for an explicit zero fee) became the payment's platform fee in the collecting balance: the Gateway fee-schedule fee posted at success (reversed_fee_minor) was given back and the deduction posted instead, in balanced journals, so the payment carries one fee. A live payment awaiting it is batched from now on; if its entries were already batched (test mode), the change lands in the next batch. Every line is stored as evidence. The aggregate id is the provider payment id. None: the payment's fee in the collecting balance is KulOffice's; read a batch (GET /v1/service/settlements/{batch_id}) for its line.
payment.fee_instruction_overdue v1 no no A live collecting payment has waited longer than the configured delay (24 hours by default) for KulOffice's fee instruction (IG-80). Its ledger entries stay out of every settlement batch until a complete instruction is accepted; the Gateway never assumes a fee. Emitted once per payment, to the platform service event sink only (never to a merchant endpoint), for reconciliation and action-required. The aggregate id is the provider payment id. KulOffice: capture and recognize the payment's fees and PUT /v1/service/payments/{provider_payment_id}/fee-instruction; otherwise open an action-required reconciliation item.

payment.fee_adjustment_accepted

A KulOffice fee adjustment of an assessment in a collecting payment's accepted fee instruction was accepted (IG-80). amount_minor is signed: positive raises the fee, negative lowers it. effect platform_fee: the payment was not batched yet, the fee change joins its batch. effect carried: the payment was already batched, so the fee change is a movement of the next collecting batch. effect record_only: kept as evidence (the assessment is not a merchant settlement deduction). The aggregate id is the provider payment id. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.fee_adjustment_accepted/v1",
  "title": "payment.fee_adjustment_accepted",
  "description": "A KulOffice fee adjustment of an assessment in a collecting payment's accepted fee instruction was accepted (IG-80). amount_minor is signed: positive raises the fee, negative lowers it. effect platform_fee: the payment was not batched yet, the fee change joins its batch. effect carried: the payment was already batched, so the fee change is a movement of the next collecting batch. effect record_only: kept as evidence (the assessment is not a merchant settlement deduction). The aggregate id is the provider payment id.",
  "x-kulpay": {
    "aggregate": "payment_fee_instruction",
    "webhook": false,
    "terminal": false,
    "act": "None: the fee entry is in the payment's next batch line (GET /v1/service/settlements/{batch_id})."
  },
  "type": "object",
  "required": [
    "provider_payment_id",
    "account_id",
    "currency",
    "adjustment_id",
    "instruction_id",
    "assessment_id",
    "amount_minor",
    "effect"
  ],
  "properties": {
    "provider_payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "adjustment_id": {
      "type": "string"
    },
    "instruction_id": {
      "type": "string"
    },
    "assessment_id": {
      "type": "string"
    },
    "amount_minor": {
      "type": "integer"
    },
    "effect": {
      "type": "string",
      "enum": [
        "platform_fee",
        "carried",
        "record_only"
      ]
    }
  },
  "additionalProperties": false
}

payment.fee_instruction_accepted

KulOffice's complete fee instruction for a collecting payment was accepted (IG-80). The sum of its merchant-borne settlement_deduction lines (deduction_minor, 0 for an explicit zero fee) became the payment's platform fee in the collecting balance: the Gateway fee-schedule fee posted at success (reversed_fee_minor) was given back and the deduction posted instead, in balanced journals, so the payment carries one fee. A live payment awaiting it is batched from now on; if its entries were already batched (test mode), the change lands in the next batch. Every line is stored as evidence. The aggregate id is the provider payment id. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.fee_instruction_accepted/v1",
  "title": "payment.fee_instruction_accepted",
  "description": "KulOffice's complete fee instruction for a collecting payment was accepted (IG-80). The sum of its merchant-borne settlement_deduction lines (deduction_minor, 0 for an explicit zero fee) became the payment's platform fee in the collecting balance: the Gateway fee-schedule fee posted at success (reversed_fee_minor) was given back and the deduction posted instead, in balanced journals, so the payment carries one fee. A live payment awaiting it is batched from now on; if its entries were already batched (test mode), the change lands in the next batch. Every line is stored as evidence. The aggregate id is the provider payment id.",
  "x-kulpay": {
    "aggregate": "payment_fee_instruction",
    "webhook": false,
    "terminal": false,
    "act": "None: the payment's fee in the collecting balance is KulOffice's; read a batch (GET /v1/service/settlements/{batch_id}) for its line."
  },
  "type": "object",
  "required": [
    "provider_payment_id",
    "account_id",
    "currency",
    "instruction_id",
    "effect",
    "deduction_minor",
    "reversed_fee_minor",
    "line_count"
  ],
  "properties": {
    "provider_payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "instruction_id": {
      "type": "string"
    },
    "effect": {
      "type": "string",
      "enum": [
        "deducted"
      ]
    },
    "deduction_minor": {
      "type": "integer"
    },
    "reversed_fee_minor": {
      "type": "integer"
    },
    "line_count": {
      "type": "integer"
    }
  },
  "additionalProperties": false
}

payment.fee_instruction_overdue

A live collecting payment has waited longer than the configured delay (24 hours by default) for KulOffice's fee instruction (IG-80). Its ledger entries stay out of every settlement batch until a complete instruction is accepted; the Gateway never assumes a fee. Emitted once per payment, to the platform service event sink only (never to a merchant endpoint), for reconciliation and action-required. The aggregate id is the provider payment id. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/payment.fee_instruction_overdue/v1",
  "title": "payment.fee_instruction_overdue",
  "description": "A live collecting payment has waited longer than the configured delay (24 hours by default) for KulOffice's fee instruction (IG-80). Its ledger entries stay out of every settlement batch until a complete instruction is accepted; the Gateway never assumes a fee. Emitted once per payment, to the platform service event sink only (never to a merchant endpoint), for reconciliation and action-required. The aggregate id is the provider payment id.",
  "x-kulpay": {
    "aggregate": "payment_fee_instruction",
    "webhook": false,
    "terminal": false,
    "act": "KulOffice: capture and recognize the payment's fees and PUT /v1/service/payments/{provider_payment_id}/fee-instruction; otherwise open an action-required reconciliation item."
  },
  "type": "object",
  "required": [
    "provider_payment_id",
    "account_id",
    "currency",
    "gross_minor",
    "awaiting_since",
    "overdue_after_seconds"
  ],
  "properties": {
    "provider_payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "gross_minor": {
      "type": "integer"
    },
    "status": {
      "type": "string"
    },
    "awaiting_since": {
      "type": "string",
      "format": "date-time"
    },
    "overdue_after_seconds": {
      "type": "integer"
    }
  },
  "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"
}

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 v3 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. v3: and its partner type from the catalogue (SETUP-5), whose template fills the technical setup. 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. v3: and its partner type from the catalogue (SETUP-5), whose template fills the technical setup. Schema (JSON)

Schema v3
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp.application.submitted/v3",
  "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. v3: and its partner type from the catalogue (SETUP-5), whose template fills the technical setup.",
  "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.v3.json"
    }
  },
  "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.technical_contact.replaced v1 no no A portal admin replaced the technical contact (KO-2), with a reason and the consent that the previous contact's access to every KulPay platform is revoked; user_id is the new contact, previous the old one's address, masked. None.
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.technical_contact.replaced

A portal admin replaced the technical contact (KO-2), with a reason and the consent that the previous contact's access to every KulPay platform is revoked; user_id is the new contact, previous the old one's address, masked. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/psp_portal.technical_contact.replaced/v1",
  "title": "psp_portal.technical_contact.replaced",
  "description": "A portal admin replaced the technical contact (KO-2), with a reason and the consent that the previous contact's access to every KulPay platform is revoked; user_id is the new contact, previous the old one's address, masked.",
  "x-kulpay": {
    "aggregate": "psp_portal_user",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "psp_id",
    "user_id",
    "replaced_by",
    "reason",
    "previous",
    "consent"
  ],
  "properties": {
    "psp_id": {
      "type": "string"
    },
    "user_id": {
      "type": "string"
    },
    "replaced_by": {
      "type": "string"
    },
    "reason": {
      "type": "string"
    },
    "previous": {
      "type": "string"
    },
    "consent": {
      "type": "boolean"
    }
  },
  "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.submitted v1 no no A reconciled regulatory report was marked submitted to the regulator (PSP-10.6). 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.submitted

A reconciled regulatory report was marked submitted to the regulator (PSP-10.6). Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/regulatory_report.submitted/v1",
  "title": "regulatory_report.submitted",
  "description": "A reconciled regulatory report was marked submitted to the regulator (PSP-10.6).",
  "x-kulpay": {
    "aggregate": "regulatory_run",
    "webhook": false,
    "terminal": false,
    "act": "None."
  },
  "type": "object",
  "required": [
    "run_id",
    "code",
    "sha256",
    "submitted_by"
  ],
  "properties": {
    "run_id": {
      "type": "string"
    },
    "code": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "submitted_by": {
      "type": "string"
    }
  },
  "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
}

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 v2 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: with collecting auto-payout on, the net is paid out next (status created); with it off (the default, G14), the batch stops at ready and is settled outside the gateway once an operator's record of it is approved; either way it is 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.ready v1 no no A managed pass-through settlement batch was built from the merchant's settlement arrangement (plan phase 8): immutable source lines for each payable's gross, platform fee, provider cost and reserve, its adjustments, reserves released and receivables offset, and the net. The gateway does not execute it: settlement through the arrangement's destination is the next step, pending the owner's decision. Fetch the batch (GET /v1/settlements/{id}) to reconcile the lines.
settlement.batch.settled v2 no no A ready settlement batch was recorded as settled outside the gateway, with the external reference as evidence and a second person's approval. Managed pass-through (plan phase 8): its payables are settled. Collecting (auto-payout off, G14): its net leaves the merchant's available balance, as its payout would have. The gateway moved no money. None: the batch (and, for a managed batch, its payables) is settled.

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: with collecting auto-payout on, the net is paid out next (status created); with it off (the default, G14), the batch stops at ready and is settled outside the gateway once an operator's record of it is approved; either way it is 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 v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/settlement.batch.created/v2",
  "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: with collecting auto-payout on, the net is paid out next (status created); with it off (the default, G14), the batch stops at ready and is settled outside the gateway once an operator's record of it is approved; either way it is 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",
        "ready",
        "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.ready

A managed pass-through settlement batch was built from the merchant's settlement arrangement (plan phase 8): immutable source lines for each payable's gross, platform fee, provider cost and reserve, its adjustments, reserves released and receivables offset, and the net. The gateway does not execute it: settlement through the arrangement's destination is the next step, pending the owner's decision. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/settlement.batch.ready/v1",
  "title": "settlement.batch.ready",
  "description": "A managed pass-through settlement batch was built from the merchant's settlement arrangement (plan phase 8): immutable source lines for each payable's gross, platform fee, provider cost and reserve, its adjustments, reserves released and receivables offset, and the net. The gateway does not execute it: settlement through the arrangement's destination is the next step, pending the owner's decision.",
  "x-kulpay": {
    "aggregate": "settlement_batch",
    "webhook": false,
    "terminal": false,
    "act": "Fetch the batch (GET /v1/settlements/{id}) to reconcile the lines."
  },
  "type": "object",
  "required": [
    "batch_id",
    "account_id",
    "currency",
    "net",
    "status",
    "gross",
    "fees",
    "refunds",
    "adjustments",
    "reserves",
    "net_minor",
    "line_count",
    "cycle_date",
    "arrangement_id",
    "arrangement_version"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "net": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "ready"
      ]
    },
    "gross": {
      "type": "string"
    },
    "fees": {
      "type": "string"
    },
    "refunds": {
      "type": "string"
    },
    "adjustments": {
      "type": "string"
    },
    "reserves": {
      "type": "string"
    },
    "net_minor": {
      "type": "integer"
    },
    "line_count": {
      "type": "integer"
    },
    "cycle_date": {
      "type": "string"
    },
    "arrangement_id": {
      "type": "string"
    },
    "arrangement_version": {
      "type": "integer"
    },
    "destination_ref": {
      "type": "string"
    }
  },
  "additionalProperties": false
}

settlement.batch.settled

A ready settlement batch was recorded as settled outside the gateway, with the external reference as evidence and a second person's approval. Managed pass-through (plan phase 8): its payables are settled. Collecting (auto-payout off, G14): its net leaves the merchant's available balance, as its payout would have. The gateway moved no money. Schema (JSON)

Schema v2
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/settlement.batch.settled/v2",
  "title": "settlement.batch.settled",
  "description": "A ready settlement batch was recorded as settled outside the gateway, with the external reference as evidence and a second person's approval. Managed pass-through (plan phase 8): its payables are settled. Collecting (auto-payout off, G14): its net leaves the merchant's available balance, as its payout would have. The gateway moved no money.",
  "x-kulpay": {
    "aggregate": "settlement_batch",
    "webhook": false,
    "terminal": false,
    "act": "None: the batch (and, for a managed batch, its payables) is settled."
  },
  "type": "object",
  "required": [
    "batch_id",
    "account_id",
    "currency",
    "net",
    "status",
    "external_reference",
    "net_minor"
  ],
  "properties": {
    "batch_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "net": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "settled"
      ]
    },
    "external_reference": {
      "type": "string"
    },
    "net_minor": {
      "type": "integer"
    },
    "approval_id": {
      "type": "string"
    },
    "approved_by": {
      "type": "string"
    },
    "funds_flow": {
      "type": "string",
      "enum": [
        "collecting",
        "managed"
      ]
    }
  },
  "additionalProperties": false
}

merchant_receivable events

EventWebhookTerminalFires whenWhat to do
settlement.receivable.created v1 no no A merchant owes managed pass-through money back (plan phase 8): a refund or reversal after the payable was batched or settled, or the retained fee and cost of a payment refunded beyond its payable. It is never a negative balance: under offset_future_settlements later batches deduct it; under invoice it waits for a KulOffice service invoice. Reconcile it against later settlement batches or the service invoice.

settlement.receivable.created

A merchant owes managed pass-through money back (plan phase 8): a refund or reversal after the payable was batched or settled, or the retained fee and cost of a payment refunded beyond its payable. It is never a negative balance: under offset_future_settlements later batches deduct it; under invoice it waits for a KulOffice service invoice. Schema (JSON)

Schema v1
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "kulpay://events/settlement.receivable.created/v1",
  "title": "settlement.receivable.created",
  "description": "A merchant owes managed pass-through money back (plan phase 8): a refund or reversal after the payable was batched or settled, or the retained fee and cost of a payment refunded beyond its payable. It is never a negative balance: under offset_future_settlements later batches deduct it; under invoice it waits for a KulOffice service invoice.",
  "x-kulpay": {
    "aggregate": "merchant_receivable",
    "webhook": false,
    "terminal": false,
    "act": "Reconcile it against later settlement batches or the service invoice."
  },
  "type": "object",
  "required": [
    "receivable_id",
    "payable_id",
    "payment_id",
    "account_id",
    "currency",
    "kind",
    "amount_minor",
    "policy"
  ],
  "properties": {
    "receivable_id": {
      "type": "string"
    },
    "payable_id": {
      "type": "string"
    },
    "payment_id": {
      "type": "string"
    },
    "account_id": {
      "type": "string"
    },
    "currency": {
      "type": "string"
    },
    "kind": {
      "type": "string",
      "enum": [
        "refund_after_settlement",
        "reversal_after_settlement",
        "payable_shortfall"
      ]
    },
    "amount_minor": {
      "type": "integer"
    },
    "policy": {
      "type": "string",
      "enum": [
        "offset_future_settlements",
        "invoice"
      ]
    }
  },
  "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
}