KulPayAPI Live

API reference

Events

Every state change as an immutable, ordered event. The same envelope is delivered by webhook and served here.

GET /v1/event-types

Event types an endpoint can subscribe to

Every webhook event type, with when it is sent and whether it is final. Requires events:read or webhooks:manage.

Responses

  • 200 {data: [{type, when, terminal}], total}.

Request

curl -X GET "$KULPAY_URL/v1/event-types" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "terminal": true,
      "type": "payment.succeeded",
      "when": "The payment settled and the money is captured."
    },
    {
      "terminal": true,
      "type": "payment.failed",
      "when": "The payment was declined or could not be completed."
    }
  ],
  "total": 2
}

GET /v1/events

List events, or catch up after an event

Two modes.

Browse (no since): newest first, filtered by type, aggregate_id and created[gte|lte], paged with starting_after.

Catch up (since=<event id>, or since= for the beginning): every later event, oldest first. Use it to recover anything a webhook endpoint missed. The newest 2 seconds are held back so no event is ever skipped by a transaction still committing.

Events of another account are never returned. Requires events:read.

Parameters

NameInTypeDescription
sincequerystring Event id to continue after (catch-up mode).
typequerystring Event type; repeat or comma-separate for several.
aggregate_idquerystring
created[gte]querystring RFC 3339 or Unix seconds.
created[lte]querystring RFC 3339 or Unix seconds.
starting_afterquerystring
limitqueryinteger
directionquerystring inbound, outbound or internal (PSP-7.1).
One of: inbound, outbound, internal
patternquerystring The interaction pattern (PSP-7.1).
One of: outbound_sync, outbound_async, outbound_notification, inbound_request, inbound_callback, inbound_file, outbound_file, status_query

Responses

Request

curl -X GET "$KULPAY_URL/v1/events" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "actor": {
        "id": "system",
        "type": "system"
      },
      "aggregate_id": "3KWPm4BeofWfvUSB1Cnv95jEq01",
      "aggregate_type": "payment",
      "created_at": "2026-10-10T22:15:06.718012Z",
      "data": {
        "attempt": 1,
        "detail": {
          "endpoints": 0,
          "event_id": "3KWPm3lMXjY9xox1gUUd8AoRJNc",
          "event_type": "payment.succeeded"
        },
        "duration_ms": 0,
        "ended_at": "2026-10-10T22:15:06.71700216Z",
        "id": "3KWPm9Zsu3GgFBtCrrl83wZo7ML",
        "outcome": "skipped",
        "payment_id": "3KWPm4BeofWfvUSB1Cnv95jEq01",
        "reason_code": "no_endpoints",
        "stage": "notify",
        "started_at": "2026-10-10T22:15:06.71700216Z",
        "transaction_type": "SALE"
      },
      "direction": "outbound",
      "id": "3KWPm6bJc7T4d0U43IOgK0pT0vS",
      "mode": "test",
      "occurred_at": "2026-10-10T22:15:06.718012Z",
      "pattern": "outbound_sync",
      "recorded_at": "2026-10-10T22:15:06.718012Z",
      "schema_version": 1,
      "sequence": 10,
      "tenant_id": "3KWPm886QpMrxawEJqc4BES8NmT",
      "type": "transaction.stage.notify.skipped"
    }
  ],
  "has_more": true,
  "next_cursor": "3KWPm6bJc7T4d0U43IOgK0pT0vS"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "use RFC 3339 (2026-10-08T10:00:00Z) or Unix seconds",
  "error": {
    "code": "invalid_request",
    "message": "use RFC 3339 (2026-10-08T10:00:00Z) or Unix seconds"
  },
  "status": 400,
  "title": "Invalid created[gte]",
  "type": "about:blank"
}
403 Error · insufficient_scope application/problem+json
{
  "code": "insufficient_scope",
  "detail": "missing required scope: events:read",
  "error": {
    "code": "insufficient_scope",
    "message": "missing required scope: events:read",
    "missing_scope": "events:read"
  },
  "missing_scope": "events:read",
  "status": 403,
  "title": "Forbidden",
  "type": "about:blank"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No event with that id exists for this account.",
  "error": {
    "code": "not_found",
    "message": "No event with that id exists for this account."
  },
  "status": 404,
  "title": "Unknown cursor",
  "type": "about:blank"
}
503 Error · service_unavailable application/problem+json
{
  "code": "service_unavailable",
  "detail": "No event store in this deployment (service_unavailable).",
  "error": {
    "code": "service_unavailable",
    "message": "No event store in this deployment (service_unavailable)."
  },
  "status": 503,
  "title": "Service Unavailable",
  "type": "about:blank"
}

GET /v1/events/{id}

Get one event

An event of another account or mode is reported as not found. Requires events:read.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

  • 200 The event.
  • 404 No such event (not_found).

Request

curl -X GET "$KULPAY_URL/v1/events/{id}" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "actor": {
    "id": "535163a20e6ce0cae10b379d29134a7f",
    "ip": "203.0.113.10",
    "type": "api_key",
    "user_agent": "node"
  },
  "aggregate_id": "3KWPcChMXO2voAp3Vdqu6ZLEMQv",
  "aggregate_type": "payment",
  "chain_seq": 320,
  "correlation_id": "b1768c81bbe474585ca1474a",
  "created_at": "2026-10-10T22:13:47.674035Z",
  "data": {
    "attempt": 1,
    "duration_ms": 3,
    "ended_at": "2026-10-10T22:13:47.643753809Z",
    "id": "3KWPc7hVNdQdhtS532Wttp6sORT",
    "outcome": "passed",
    "payment_id": "3KWPcChMXO2voAp3Vdqu6ZLEMQv",
    "stage": "enrich_validate",
    "started_at": "2026-10-10T22:13:47.640372197Z",
    "transaction_type": "SALE"
  },
  "direction": "inbound",
  "hash": "504e00e8a3ab657f3dbbbbdc52542302f9e54b7f03142cb24757999cff2a10bc",
  "id": "3KWPcDFQR4Iy59TZIVI6Hf51z3k",
  "idempotency_key": "ux-1791670427346",
  "mode": "test",
  "occurred_at": "2026-10-10T22:13:47.674035Z",
  "pattern": "inbound_request",
  "prev_hash": "d88f24bf1fbd0b40ce5736661f2e46f09aee5faf699c836c1322b34cc46a324d",
  "recorded_at": "2026-10-10T22:13:47.674035Z",
  "region": "mz",
  "schema_version": 1,
  "sequence": 3,
  "tenant_id": "OcThktmhLjl2JAewdDBNXh2u0lT",
  "type": "transaction.stage.enrich_validate.passed"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "error": {
    "code": "not_found",
    "message": ""
  },
  "status": 404,
  "title": "No such event",
  "type": "about:blank"
}

POST /v1/webhook-deliveries/{id}/replay

Replay one delivery

Queues the delivery again with its frozen signed body. Requires webhooks:replay.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

  • 202 {"queued": n}: fresh deliveries on the same schedule, each event once.
  • 404 Not this account's delivery. not_found
  • 409 endpoint_disabled.

Request

curl -X POST "$KULPAY_URL/v1/webhook-deliveries/{id}/replay" \
  -H "Authorization: Bearer $KULPAY_KEY"
202 Response application/json
{
  "queued": 1
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "webhook endpoint or delivery not found",
  "error": {
    "code": "not_found",
    "message": "webhook endpoint or delivery not found"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}
409 Error · endpoint_disabled application/problem+json
{
  "code": "endpoint_disabled",
  "detail": "the endpoint is disabled: enable it before replaying",
  "error": {
    "code": "endpoint_disabled",
    "message": "the endpoint is disabled: enable it before replaying"
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}

GET /v1/webhook-endpoints

The account's webhook endpoints

Responses

  • 200 {data, total}.

Request

curl -X GET "$KULPAY_URL/v1/webhook-endpoints" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "contacts": [
        "dev@example.co.mz"
      ],
      "created_at": "2026-07-02T09:15:00Z",
      "ed25519": true,
      "enabled": true,
      "event_types": [
        "payment.succeeded",
        "payment.failed",
        "refund.succeeded"
      ],
      "id": "3K9fVrOSGHIvfS0z8BcX9qUgBf3",
      "mode": "live",
      "status": "active",
      "url": "https://loja.example.co.mz/webhooks/kulpay"
    },
    {
      "created_at": "2026-06-20T16:40:12Z",
      "disabled_at": "2026-10-01T08:00:00Z",
      "disabled_reason": "paused",
      "ed25519": false,
      "enabled": false,
      "event_types": [
        "*"
      ],
      "id": "3K4nekciqFMaDnnsmlT6qBJ8alZ",
      "mode": "test",
      "status": "paused",
      "url": "https://staging.loja.example.co.mz/webhooks/kulpay"
    }
  ],
  "total": 2
}

POST /v1/webhook-endpoints

Subscribe an endpoint to events

Registers an endpoint for the key's mode, subscribed to event types: exact names, families (payout.*) or everything (omit, or *) (PSP-7.5). Deliveries are signed and follow the 24-hour schedule and dead-letter queue of all webhooks. The signing secret is returned once. Records webhook_endpoint.subscribed. Requires webhooks:manage. Subscribe to partner.alert (or receive everything) to get KulPay's operational alerts on this endpoint too, besides the email to your contacts (P-2): an API key or client certificate expiring, a key found in public, a statement not received.

Request body application/json required

FieldTypeDescription
contactsstring (email)[]
event_typesstring[]
url requiredstring

Responses

  • 201 The endpoint.
  • 400 An unknown event type or a bad address. invalid_request

Request

curl -X POST "$KULPAY_URL/v1/webhook-endpoints" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "contacts": [
    "ops@example.co.mz"
  ],
  "event_types": [
    "payment.succeeded"
  ],
  "url": "https://merchant.example.co.mz/kulpay/return"
}'
201 Response application/json
{
  "contacts": [
    "string"
  ],
  "created_at": "2026-10-10T09:30:00Z",
  "disabled_reason": "paused",
  "enabled": true,
  "event_types": [
    "payment.succeeded"
  ],
  "id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "mode": "test",
  "secret": "string",
  "status": "active",
  "url": "https://merchant.example.co.mz/kulpay/return"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "\"payment.completed\" is not a webhook event type (GET /v1/event-types lists them)",
  "error": {
    "code": "invalid_request",
    "message": "\"payment.completed\" is not a webhook event type (GET /v1/event-types lists them)"
  },
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}

GET /v1/webhook-endpoints/{id}

A webhook endpoint

Parameters

NameInTypeDescription
id requiredpathstring

Responses

Request

curl -X GET "$KULPAY_URL/v1/webhook-endpoints/{id}" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "contacts": [
    "ops@example.co.mz"
  ],
  "created_at": "2026-10-10T09:30:00Z",
  "disabled_reason": "paused",
  "enabled": true,
  "event_types": [
    "payment.succeeded",
    "bill_payment.succeeded"
  ],
  "id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "mode": "test",
  "secret": "string",
  "status": "active",
  "url": "https://loja.example.co.mz/webhooks/kulpay"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "webhook endpoint or delivery not found",
  "error": {
    "code": "not_found",
    "message": "webhook endpoint or delivery not found"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

PATCH /v1/webhook-endpoints/{id}

Change an endpoint's subscription

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
event_typesstring[]

Responses

Request

curl -X PATCH "$KULPAY_URL/v1/webhook-endpoints/{id}" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_types": [
    "payment.succeeded"
  ]
}'
200 Response application/json
{
  "contacts": [
    "string"
  ],
  "created_at": "2026-10-10T09:30:00Z",
  "disabled_reason": "paused",
  "enabled": true,
  "event_types": [
    "payment.succeeded"
  ],
  "id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "mode": "test",
  "secret": "string",
  "status": "active",
  "url": "https://merchant.example.co.mz/kulpay/return"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "\"payment.completed\" is not a webhook event type (GET /v1/event-types lists them)",
  "error": {
    "code": "invalid_request",
    "message": "\"payment.completed\" is not a webhook event type (GET /v1/event-types lists them)"
  },
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}

DELETE /v1/webhook-endpoints/{id}

Remove a webhook endpoint

Parameters

NameInTypeDescription
id requiredpathstring

Responses

  • 204 Removed.

Request

curl -X DELETE "$KULPAY_URL/v1/webhook-endpoints/{id}" \
  -H "Authorization: Bearer $KULPAY_KEY"

PUT /v1/webhook-endpoints/{id}/contacts

Who is alerted about an endpoint

Emails told when a delivery is dead-lettered or the endpoint disabled; without any, a PSP's technical contact. Requires webhooks:manage.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
emailsstring (email)[]

Responses

Request

curl -X PUT "$KULPAY_URL/v1/webhook-endpoints/{id}/contacts" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "emails": [
    "ops@example.co.mz"
  ]
}'
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "\"suporte.example.co.mz\" is not an email address",
  "error": {
    "code": "invalid_request",
    "message": "\"suporte.example.co.mz\" is not an email address"
  },
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}

GET /v1/webhook-endpoints/{id}/deliveries

An endpoint's delivery history

The last 30 days, newest first: status, attempts, response code, latency, next attempt.

Parameters

NameInTypeDescription
id requiredpathstring
limitqueryinteger

Responses

  • 200 {data, total}.

Request

curl -X GET "$KULPAY_URL/v1/webhook-endpoints/{id}/deliveries" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "attempts": 2,
      "created_at": "2026-10-11T09:12:03Z",
      "duration_ms": 10004,
      "event_id": "3KP9ygtnmJTauCTmOray77nyB4C",
      "event_type": "payment.failed",
      "id": "3KwftWaywR8E0sQgebUy55IidZY",
      "last_error": "HTTP 503",
      "next_attempt_at": "2026-10-11T09:17:03Z",
      "response_code": 503,
      "status": "pending"
    },
    {
      "attempts": 1,
      "created_at": "2026-10-11T08:47:21Z",
      "delivered_at": "2026-10-11T08:47:21Z",
      "duration_ms": 184,
      "event_id": "3K15E3LnuKxuG278PTGllbABGwy",
      "event_type": "payment.succeeded",
      "id": "3K7dZCinKGJgHK9WSjj79t6IWaX",
      "response_code": 200,
      "status": "delivered"
    }
  ],
  "total": 2
}

POST /v1/webhook-endpoints/{id}/enable

Enable a disabled endpoint

Clears the disable reason (gone, dead_letters); records webhook_endpoint.enabled. Requires webhooks:manage.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

  • 200 The endpoint, enabled: the same representation as GET /v1/webhook-endpoints/{id}.
  • 404 Not this account's endpoint. not_found

Request

curl -X POST "$KULPAY_URL/v1/webhook-endpoints/{id}/enable" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "contacts": [
    "ops@example.co.mz"
  ],
  "created_at": "2026-10-10T09:30:00Z",
  "enabled": true,
  "event_types": [
    "payment.succeeded",
    "bill_payment.succeeded"
  ],
  "id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "mode": "test",
  "status": "active",
  "url": "https://loja.example.co.mz/webhooks/kulpay"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "webhook endpoint or delivery not found",
  "error": {
    "code": "not_found",
    "message": "webhook endpoint or delivery not found"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

POST /v1/webhook-endpoints/{id}/pause

Pause an endpoint

Nothing is sent while paused; resume with /enable, then replay what it missed from an event. Records webhook_endpoint.paused.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

  • 200 The endpoint.

Request

curl -X POST "$KULPAY_URL/v1/webhook-endpoints/{id}/pause" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "contacts": [
    "string"
  ],
  "created_at": "2026-10-10T09:30:00Z",
  "disabled_reason": "paused",
  "enabled": true,
  "event_types": [
    "payment.succeeded"
  ],
  "id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "mode": "test",
  "secret": "string",
  "status": "active",
  "url": "https://merchant.example.co.mz/kulpay/return"
}

POST /v1/webhook-endpoints/{id}/replay

Replay an endpoint's deliveries

Webhooks are tried immediately, then 1 min, 5 min, 15 min, 1 h, 3 h, 8 h and 24 h after the first attempt (10 s timeout each; any 2xx is success). The last failure dead-letters the delivery and alerts the endpoint's contacts; a 410 disables the endpoint, as do three days running with dead-lettered deliveries (PSP-9.5). This queues again every event from since on (default the last 30 days), or the dead-lettered ones (status=exhausted). Records delivery.replayed. Requires webhooks:replay.

Parameters

NameInTypeDescription
id requiredpathstring
sincequerystring An event id: replay it and everything after.
statusquerystring
One of: exhausted, delivered

Responses

  • 202 {"queued": n}: fresh deliveries on the same schedule, each event once.
  • 404 Not this account's endpoint. not_found
  • 409 endpoint_disabled: enable it first.

Request

curl -X POST "$KULPAY_URL/v1/webhook-endpoints/{id}/replay" \
  -H "Authorization: Bearer $KULPAY_KEY"
202 Response application/json
{
  "queued": 14
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "webhook endpoint or delivery not found",
  "error": {
    "code": "not_found",
    "message": "webhook endpoint or delivery not found"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}
409 Error · endpoint_disabled application/problem+json
{
  "code": "endpoint_disabled",
  "detail": "the endpoint is disabled: enable it before replaying",
  "error": {
    "code": "endpoint_disabled",
    "message": "the endpoint is disabled: enable it before replaying"
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}

POST /v1/webhook-endpoints/{id}/roll-secret

Roll an endpoint's signing secret

A new secret (shown once); the old one keeps signing until previous_secret_expires_at (default 24 h, at most 7 days), so each delivery meanwhile carries a v1= per secret in KulPay-Signature (PSP-8.8). Records credential.webhook_secret.rotated. See the "Rotate a webhook signing secret" runbook.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json

FieldTypeDescription
gracestring

Responses

  • 200 {endpoint (with secret), previous_secret_expires_at}.
  • 400 A grace under 1 minute or over 7 days. invalid_request

Request

curl -X POST "$KULPAY_URL/v1/webhook-endpoints/{id}/roll-secret" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "grace": "24h"
}'
200 Response application/json
{
  "endpoint": {
    "contacts": [
      "dev@example.co.mz"
    ],
    "created_at": "2026-05-04T14:22:10Z",
    "ed25519": false,
    "enabled": true,
    "event_types": [
      "payment.succeeded",
      "payment.failed"
    ],
    "id": "3K4lgJuN0uZKZkh8qikNmN22sbx",
    "mode": "live",
    "previous_secret_expires_at": "2026-10-12T10:30:00Z",
    "secret": "whsec_…",
    "status": "active",
    "url": "https://loja.example.co.mz/webhooks/kulpay"
  },
  "previous_secret_expires_at": "2026-10-12T10:30:00Z"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "the grace period is between 1 minute and 7 days",
  "error": {
    "code": "invalid_request",
    "message": "the grace period is between 1 minute and 7 days"
  },
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}

PUT /v1/webhook-endpoints/{id}/schema-versions

Pin event types to schema versions

Events are recorded at the latest schema version of their type (each envelope carries schema_version). Pin an event type to a version and this endpoint receives it in that version, converted by KulPay, for as long as the version is supported; pinning the current version shields you from the next one. An event the pinned version cannot express (a status it did not have) arrives in the current version, its envelope saying which. When a version is scheduled to retire, the endpoint's contacts are emailed 90 days before (webhook_endpoint.schema_retiring, stage notice) and from the date the type arrives in its current version (stage retired). GET /v1/webhook-schema-versions lists the versions, which can be pinned and when they retire. Replaces the endpoint's pins; {} unpins every type. Records webhook_endpoint.schema_pinned. Requires webhooks:manage.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
schema_versions requiredobject Event types pinned to a schema version (F3-18).

Responses

  • 200 The endpoint.
  • 400 The request body or query failed validation. The response body lists each field-level issue. invalid_request
  • 404 The named resource was not found. not_found

Request

curl -X PUT "$KULPAY_URL/v1/webhook-endpoints/{id}/schema-versions" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "schema_versions": {
    "bill_invoice.paid": 1,
    "payment.succeeded": 2
  }
}'
200 Response application/json
{
  "contacts": [
    "string"
  ],
  "created_at": "2026-10-10T09:30:00Z",
  "disabled_reason": "paused",
  "enabled": true,
  "event_types": [
    "payment.succeeded"
  ],
  "id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "mode": "test",
  "status": "active",
  "url": "https://merchant.example.co.mz/kulpay/return"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "The request body failed validation.",
  "error": {
    "code": "invalid_request",
    "errors": [
      {
        "field": "money.amount",
        "message": "must be a positive decimal with at most 2 places"
      }
    ],
    "message": "The request body failed validation."
  },
  "errors": [
    {
      "field": "money.amount",
      "message": "must be a positive decimal with at most 2 places"
    }
  ],
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No payment with this id.",
  "error": {
    "code": "not_found",
    "message": "No payment with this id."
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

PUT /v1/webhook-endpoints/{id}/signing

Ed25519 signatures

With ed25519: true deliveries also carry v1e=<base64 Ed25519 signature of "<t>.<body>">, verifiable with the region's key from /.well-known/kulpay-webhook-keys.

Parameters

NameInTypeDescription
id requiredpathstring

Request body application/json required

FieldTypeDescription
ed25519boolean

Responses

  • 200 The endpoint.

Request

curl -X PUT "$KULPAY_URL/v1/webhook-endpoints/{id}/signing" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ed25519": true
}'
200 Response application/json
{
  "contacts": [
    "dev@example.co.mz"
  ],
  "created_at": "2026-05-04T14:22:10Z",
  "ed25519": true,
  "enabled": true,
  "event_types": [
    "payment.succeeded",
    "payment.failed"
  ],
  "id": "3K4lgJuN0uZKZkh8qikNmN22sbx",
  "mode": "live",
  "status": "active",
  "url": "https://loja.example.co.mz/webhooks/kulpay"
}

POST /v1/webhook-endpoints/{id}/test

Send a test event

A signed webhook_endpoint.tested event to this endpoint only, through the normal delivery path.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

Request

curl -X POST "$KULPAY_URL/v1/webhook-endpoints/{id}/test" \
  -H "Authorization: Bearer $KULPAY_KEY"
202 Response application/json
{
  "delivery_id": "3K7dZCinKGJgHK9WSjj79t6IWaX",
  "event_id": "3K15E3LnuKxuG278PTGllbABGwy",
  "event_type": "webhook_endpoint.tested"
}
409 Error · endpoint_disabled application/problem+json
{
  "code": "endpoint_disabled",
  "detail": "the endpoint is disabled: enable it before replaying",
  "error": {
    "code": "endpoint_disabled",
    "message": "the endpoint is disabled: enable it before replaying"
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}

GET /v1/webhook-schema-versions

Webhook schema versions

Events are recorded at the latest schema version of their type (each envelope carries schema_version). Pin an event type to a version and this endpoint receives it in that version, converted by KulPay, for as long as the version is supported; pinning the current version shields you from the next one. An event the pinned version cannot express (a status it did not have) arrives in the current version, its envelope saying which. When a version is scheduled to retire, the endpoint's contacts are emailed 90 days before (webhook_endpoint.schema_retiring, stage notice) and from the date the type arrives in its current version (stage retired). GET /v1/webhook-schema-versions lists the versions, which can be pinned and when they retire. Requires webhooks:manage or admin:read.

Responses

  • 200 {data, total}.

Request

curl -X GET "$KULPAY_URL/v1/webhook-schema-versions" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "current": true,
      "event_type": "payment.succeeded",
      "pinnable": true,
      "retires_at": "2026-10-10T09:30:00Z",
      "version": 1
    }
  ],
  "total": 12
}