API reference
Webhook endpoints
Endpoints we send signed invoice and correction events to. The receiving side is covered in the Webhooks guide.
The WebhookEndpoint object
Where we send signed events. The whsec_... secret appears only in the create response.
Attributes
idstringrequiredEndpoint id.
Prefix
we_objectstringrequiredObject type.
Always
webhook_endpointlivemodebooleanrequiredThe endpoint's mode.
urlstringrequiredReceiver URL.
enabled_eventsarray of stringrequiredSubscribed event types.
descriptionstring | nullNote.
statusstringrequiredEndpoint state.
One of:
enabled,disabledcreatedintegerrequiredCreated (Unix seconds).
{
"id": "we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7",
"object": "webhook_endpoint",
"livemode": false,
"url": "https://example.com/ksef/webhook",
"enabled_events": [
"*"
],
"description": "Shop backend",
"status": "enabled",
"created": 1760865185
}List webhook endpoints
GET /v1/webhook_endpoints
Active endpoints in the key's mode. Secrets are not included.
Responses
- 200The endpoints.
- 401Missing or invalid key:
api_key_missingapi_key_invalid
curl https://app.ksefshop.pl/v1/webhook_endpoints \
-H "Authorization: Bearer $KSEF_API_KEY"<?php
$ch = curl_init('https://app.ksefshop.pl/v1/webhook_endpoints');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('KSEF_API_KEY'),
],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
echo count($data['data']), ' objects', PHP_EOL;const res = await fetch("https://app.ksefshop.pl/v1/webhook_endpoints", {
headers: {
Authorization: `Bearer ${process.env.KSEF_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data.data.length, data.has_more);import os
import requests
resp = requests.get(
"https://app.ksefshop.pl/v1/webhook_endpoints",
headers={
"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}",
},
timeout=30,
)
data = resp.json()
if not resp.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(len(data["data"]), data["has_more"]){
"object": "list",
"url": "/v1/webhook_endpoints",
"has_more": false,
"data": []
}{
"error": {
"type": "authentication_error",
"code": "api_key_invalid",
"message": "The API key is wrong, revoked, or the account is disabled.",
"doc_url": "https://ksefshop.pl/api/errors#api_key_invalid"
}
}Create a webhook endpoint
POST /v1/webhook_endpoints
Registers a URL for events. The whsec_... signing secret is returned only in this response. Live mode needs an https:// URL; test mode also allows http://. At most 16 endpoints per mode.
Parameters
Idempotency-Keyheader · stringAny unique text (1-255 characters), e.g.
order-SO-1001. A retry with the same key within 24 hours returns the first response. Idempotency
Request body (JSON)
urlstringrequiredhttps only in live mode.
enabled_eventsarray of stringrequiredEvent types, or
*for all.One of:
invoice.accepted,invoice.rejected,invoice.skipped,correction.accepted,correction.rejected,correction.skipped,*· 1-∞ itemsdescriptionstringA note for yourself.
max 500 characters
Responses
- 201The endpoint with its secret.
- 400Invalid request, e.g.
parameter_invalidbuyer_nip_invalidvat_rate_unsupported - 401Missing or invalid key:
api_key_missingapi_key_invalid - 409Idempotency-Key reused with another request, or the PDF is not ready:
idempotency_key_reusedpdf_not_ready - 429Rate limit exceeded, wait
Retry-Afterseconds:rate_limited
curl -X POST https://app.ksefshop.pl/v1/webhook_endpoints \
-H "Authorization: Bearer $KSEF_API_KEY" \
-H "Idempotency-Key: webhook-shop-backend" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/ksef/webhook",
"enabled_events": [
"*"
],
"description": "Shop backend"
}'<?php
$payload = [
'url' => 'https://example.com/ksef/webhook',
'enabled_events' => ['*'],
'description' => 'Shop backend',
];
$ch = curl_init('https://app.ksefshop.pl/v1/webhook_endpoints');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('KSEF_API_KEY'),
'Idempotency-Key: webhook-shop-backend',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
echo var_export($data['id'], true), ' ', var_export($data['secret'], true), PHP_EOL;const res = await fetch("https://app.ksefshop.pl/v1/webhook_endpoints", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KSEF_API_KEY}`,
"Idempotency-Key": "webhook-shop-backend",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com/ksef/webhook",
enabled_events: ["*"],
description: "Shop backend",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data.id, data.secret);import os
import requests
resp = requests.post(
"https://app.ksefshop.pl/v1/webhook_endpoints",
headers={
"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}",
"Idempotency-Key": "webhook-shop-backend",
},
json={
"url": "https://example.com/ksef/webhook",
"enabled_events": ["*"],
"description": "Shop backend",
},
timeout=30,
)
data = resp.json()
if not resp.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data["id"], data["secret"]){
"id": "we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7",
"object": "webhook_endpoint",
"livemode": false,
"url": "https://example.com/ksef/webhook",
"enabled_events": [
"*"
],
"description": "Shop backend",
"status": "enabled",
"created": 1760865185,
"secret": "whsec_shownOnlyOnceExampleValue0000"
}{
"error": {
"type": "invalid_request_error",
"code": "buyer_nip_invalid",
"message": "buyer.nip is not a valid Polish NIP (checksum).",
"param": "order.buyer.nip",
"doc_url": "https://ksefshop.pl/api/errors#buyer_nip_invalid"
}
}{
"error": {
"type": "authentication_error",
"code": "api_key_invalid",
"message": "The API key is wrong, revoked, or the account is disabled.",
"doc_url": "https://ksefshop.pl/api/errors#api_key_invalid"
}
}{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_reused",
"message": "Idempotency-Key \"k-1002\" was already used with a different request.",
"doc_url": "https://ksefshop.pl/api/errors#idempotency_key_reused"
}
}Retrieve a webhook endpoint
GET /v1/webhook_endpoints/{id}
One endpoint by its we_... id.
Parameters
idpath · stringrequiredThe object's id.
Prefix
we_
Responses
- 200The endpoint.
- 401Missing or invalid key:
api_key_missingapi_key_invalid - 404No such object (or it belongs to another account or mode):
resource_missing
curl https://app.ksefshop.pl/v1/webhook_endpoints/we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7 \
-H "Authorization: Bearer $KSEF_API_KEY"<?php
$ch = curl_init('https://app.ksefshop.pl/v1/webhook_endpoints/we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('KSEF_API_KEY'),
],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
echo var_export($data['id'], true), ' ', var_export($data['status'], true), PHP_EOL;const res = await fetch("https://app.ksefshop.pl/v1/webhook_endpoints/we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7", {
headers: {
Authorization: `Bearer ${process.env.KSEF_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data.id, data.status);import os
import requests
resp = requests.get(
"https://app.ksefshop.pl/v1/webhook_endpoints/we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7",
headers={
"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}",
},
timeout=30,
)
data = resp.json()
if not resp.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data["id"], data["status"]){
"id": "we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7",
"object": "webhook_endpoint",
"livemode": false,
"url": "https://example.com/ksef/webhook",
"enabled_events": [
"*"
],
"description": "Shop backend",
"status": "enabled",
"created": 1760865185
}{
"error": {
"type": "authentication_error",
"code": "api_key_invalid",
"message": "The API key is wrong, revoked, or the account is disabled.",
"doc_url": "https://ksefshop.pl/api/errors#api_key_invalid"
}
}{
"error": {
"type": "not_found_error",
"code": "resource_missing",
"message": "No such invoice: 'inv_4fJ2kP9xQwErTy7uI0oPaSdF'",
"doc_url": "https://ksefshop.pl/api/errors#resource_missing"
}
}Delete a webhook endpoint
DELETE /v1/webhook_endpoints/{id}
Disables the endpoint: it gets no new events. Events stay available at /v1/events.
Parameters
idpath · stringrequiredThe object's id.
Prefix
we_
Responses
- 200Deleted.
- 401Missing or invalid key:
api_key_missingapi_key_invalid - 404No such object (or it belongs to another account or mode):
resource_missing
curl -X DELETE https://app.ksefshop.pl/v1/webhook_endpoints/we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7 \
-H "Authorization: Bearer $KSEF_API_KEY"<?php
$ch = curl_init('https://app.ksefshop.pl/v1/webhook_endpoints/we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('KSEF_API_KEY'),
],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
echo var_export($data['id'], true), ' ', var_export($data['deleted'], true), PHP_EOL;const res = await fetch("https://app.ksefshop.pl/v1/webhook_endpoints/we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.KSEF_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data.id, data.deleted);import os
import requests
resp = requests.delete(
"https://app.ksefshop.pl/v1/webhook_endpoints/we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7",
headers={
"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}",
},
timeout=30,
)
data = resp.json()
if not resp.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data["id"], data["deleted"]){
"id": "we_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7",
"object": "webhook_endpoint",
"deleted": true
}{
"error": {
"type": "authentication_error",
"code": "api_key_invalid",
"message": "The API key is wrong, revoked, or the account is disabled.",
"doc_url": "https://ksefshop.pl/api/errors#api_key_invalid"
}
}{
"error": {
"type": "not_found_error",
"code": "resource_missing",
"message": "No such invoice: 'inv_4fJ2kP9xQwErTy7uI0oPaSdF'",
"doc_url": "https://ksefshop.pl/api/errors#resource_missing"
}
}Event delivery
When an invoice or a correction reaches a final state, we POST an Event to every enabled endpoint subscribed to that event type.
| Header | Value |
|---|---|
KSeF-Signature |
t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, t + "." + raw body) |
KSeF-Event-Id |
The event id (evt_...), for dropping duplicates |
Content-Type |
application/json |
Any 2xx answer counts as delivered. Signature verification in four languages and the retry schedule are in the Webhooks guide.