Skip to main content

Webhook Integration

Receive real-time notifications about delivery events via webhooks.

Overview​

KioskForce delivery lockers can send HTTP POST requests to your server when important events occur, such as:

  • Package drop-off completed
  • Package picked up by customer
  • Reservation cancelled
  • Pickup abandoned
  • Pickup attempt failed
  • Out-of-band door opening completed (customer support)

This allows you to update your order status in real-time without polling the API.

Webhook Configuration​

Global Webhook URL​

Set up a global webhook URL during platform onboarding that will receive all events by default.

Per-Reservation Webhooks​

Override the global webhook URL for specific reservations by including webhook_url in your reservation request. The URL must use HTTPS:

{
"machine_id": "kf_tangerang_001",
"platform_order_id": "order_12345",
"webhook_url": "https://yourapi.com/webhooks/delivery",
"packaging": { ... }
}

Webhook Payload Structure​

All webhook events are wrapped in a common Payload structure that provides metadata for event tracking and idempotency.

Payload Wrapper​

Every webhook request body follows this structure:

{
"event_id": "evt_550e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "idem_abc123def456",
"event_time_unix_timestamp": 1718271000,
"event_time_timestamp": "2025-06-13T10:30:00Z",
"platform_order_id": "order_12345",
"reservation_id": "res_abc123",
"event_type": "EVENT_TYPE_DROPOFF",
"drop_off_event": { ... }
}
FieldTypeRequiredDescription
event_idstringYesUnique identifier for this event delivery
idempotency_keystringYesUnique key across multiple retries - use this for deduplication
event_time_unix_timestampint64YesUnix timestamp (seconds) when the event occurred
event_time_timestampstringYesISO 8601 timestamp when the event occurred
platform_order_idstringYesYour original order ID from the reservation
reservation_idstringYesKioskForce reservation ID
event_typeenumYesType of event (see Event Types below)
*_eventobjectYesEvent-specific data (one of the event types below)

Event Types​

Event TypeField NameDescription
EVENT_TYPE_RESERVATION_CREATEDreservation_created_eventReservation created
EVENT_TYPE_DROPOFFdrop_off_eventPackage dropped off
EVENT_TYPE_PICKUPpick_up_eventPackage picked up
EVENT_TYPE_RESERVATION_CANCELLEDreservation_cancelled_eventReservation cancelled
EVENT_TYPE_PICKUP_FAILEDpick_up_failed_eventPickup attempt failed
EVENT_TYPE_OPEN_DOOR_FULFILLEDopen_door_fulfilled_eventDoor open request completed

Common Data Structures​

Cell Object​

Represents a locker cell where packages are stored.

{
"description": "A03",
"cell_id": "cell_kf_tan_001_a03"
}
FieldTypeRequiredDescription
descriptionstringYesHuman-readable cell label (e.g., "A03", "B05")
cell_idstringYesUnique identifier for the cell within a machine

Machine Object​

Represents a delivery locker machine.

{
"machine_id": "kf_tangerang_001",
"name": "Tangerang Station Locker",
"location": "Tangerang Train Station, Main Hall",
"latitude": -6.1781,
"longitude": 106.6319,
"online": true,
"preferred_cells": [
{
"width": 400,
"height": 300,
"depth": 500,
"heating": false,
"cooling": false,
"weight_sensor": true
}
]
}
FieldTypeRequiredDescription
machine_idstringYesUnique machine identifier
namestringYesHuman-readable machine name
locationstringYesMachine location description
latitudefloatYesGPS latitude coordinate
longitudefloatYesGPS longitude coordinate
onlinebooleanYesWhether the machine is currently online
preferred_cellsarrayNoAvailable cell configurations (see CellInformation)

CellInformation Object​

Describes cell dimensions and features.

{
"width": 400,
"height": 300,
"depth": 500,
"heating": false,
"cooling": true,
"weight_sensor": true
}
FieldTypeRequiredDescription
widthint32YesCell width in millimeters
heightint32YesCell height in millimeters
depthint32YesCell depth in millimeters
heatingbooleanYesWhether the cell has heating capability
coolingbooleanYesWhether the cell has cooling capability
weight_sensorbooleanYesWhether the cell has a weight sensor

SourceType Enum​

Indicates how the user interacted with the machine.

ValueDescription
SOURCE_TYPE_DELIVERY_PLATFORMVia delivery platform API call
SOURCE_TYPE_MACHINEDirect interaction at machine (QR code scan or numeric code entry)

Webhook Events​

1. Reservation Created Event​

Triggered when a new reservation is successfully created. This event contains the codes and QR codes needed for drop-off and pickup.

{
"event_id": "evt_550e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "idem_res_created_abc123",
"event_time_unix_timestamp": 1718258400,
"event_time_timestamp": "2025-06-13T09:00:00Z",
"platform_order_id": "order_12345",
"reservation_id": "res_abc123",
"event_type": "EVENT_TYPE_RESERVATION_CREATED",
"reservation_created_event": {
"reservation_id": "res_abc123",
"machine_id": "kf_tangerang_001",
"machine": {
"machine_id": "kf_tangerang_001",
"name": "Tangerang Station Locker",
"location": "Tangerang Train Station, Main Hall",
"latitude": -6.1781,
"longitude": 106.6319,
"online": true
},
"drop_off_code": "123456",
"drop_off_qrcode": "KFDO-ABC123XYZ",
"drop_off_qrcode_url": "https://api.delivery.kioskforce.com/qrcode/KFDO-ABC123XYZ",
"pickup_code": "789012",
"pickup_code_qrcode": "KFPU-DEF456UVW",
"pickup_code_qrcode_url": "https://api.delivery.kioskforce.com/qrcode/KFPU-DEF456UVW"
}
}

Event-Specific Fields:

FieldTypeRequiredDescription
reservation_idstringYesReservation identifier
machine_idstringYesMachine identifier
machineMachineYesFull machine details
drop_off_codestringYesNumeric code for driver to unlock door
drop_off_qrcodestringYesQR code content for driver (render in app)
drop_off_qrcode_urlstringYesURL to QR code image for driver
pickup_codestringYesNumeric code for customer to unlock door
pickup_code_qrcodestringYesQR code content for customer (render in app)
pickup_code_qrcode_urlstringYesURL to QR code image for customer

Use Cases:

  • Send pickup codes to customers via SMS/email/push notification
  • Display QR codes in your mobile app for driver and customer
  • Store codes for customer support reference

2. Drop-off Event​

Triggered when a driver/rider successfully drops off a package and closes the door.

⚠️ Important Timing Note:

  • This event is sent when the locker door closes after the dropoff
  • The door opening alone does NOT trigger this event
  • This ensures the package has been successfully placed before notifying the platform
{
"event_id": "evt_660e8400-e29b-41d4-a716-446655440001",
"idempotency_key": "idem_dropoff_abc123",
"event_time_unix_timestamp": 1718271000,
"event_time_timestamp": "2025-06-13T10:30:00Z",
"platform_order_id": "order_12345",
"reservation_id": "res_abc123",
"event_type": "EVENT_TYPE_DROPOFF",
"drop_off_event": {
"source_type": "SOURCE_TYPE_MACHINE",
"cell": {
"description": "A03",
"cell_id": "cell_kf_tan_001_a03"
},
"machine": {
"machine_id": "kf_tangerang_001",
"name": "Tangerang Station Locker",
"location": "Tangerang Train Station, Main Hall",
"latitude": -6.1781,
"longitude": 106.6319,
"online": true
}
}
}

Note: Fields with empty or zero values are omitted from the actual payload. For example, request_id only appears when the action was initiated via API (SOURCE_TYPE_DELIVERY_PLATFORM).

Event-Specific Fields:

FieldTypeRequiredDescription
source_typeSourceTypeYesHow the drop-off was initiated
request_idstringNoX-Request-ID (only present if initiated via API)
cellCellYesCell where package was placed
machineMachineYesMachine details

3. Pickup Event​

Triggered when a customer successfully picks up their package.

⚠️ Important Timing Note:

  • This event is sent immediately when the locker door opens successfully
  • This is different from dropoff - the event is sent on door open, not door close
  • The reservation state also changes to PICKED_UP immediately when the door opens
{
"event_id": "evt_770e8400-e29b-41d4-a716-446655440002",
"idempotency_key": "idem_pickup_abc123",
"event_time_unix_timestamp": 1718279100,
"event_time_timestamp": "2025-06-13T12:45:00Z",
"platform_order_id": "order_12345",
"reservation_id": "res_abc123",
"event_type": "EVENT_TYPE_PICKUP",
"pick_up_event": {
"source_type": "SOURCE_TYPE_MACHINE",
"cell": {
"description": "A03",
"cell_id": "cell_kf_tan_001_a03"
},
"machine": {
"machine_id": "kf_tangerang_001",
"name": "Tangerang Station Locker",
"location": "Tangerang Train Station, Main Hall",
"latitude": -6.1781,
"longitude": 106.6319,
"online": true
}
}
}

Note: Fields with empty or zero values are omitted. request_id only appears when initiated via API.

Event-Specific Fields:

FieldTypeRequiredDescription
source_typeSourceTypeYesHow the pickup was initiated
request_idstringNoX-Request-ID (only present if initiated via API)
cellCellYesCell where package was picked up from
machineMachineYesMachine details

4. Reservation Cancelled Event​

Triggered when a reservation is cancelled before drop-off.

{
"event_id": "evt_880e8400-e29b-41d4-a716-446655440003",
"idempotency_key": "idem_cancelled_abc123",
"event_time_unix_timestamp": 1718254500,
"event_time_timestamp": "2025-06-13T08:15:00Z",
"platform_order_id": "order_12345",
"reservation_id": "res_abc123",
"event_type": "EVENT_TYPE_RESERVATION_CANCELLED",
"reservation_cancelled_event": {
"reservation_id": "res_abc123",
"machine_id": "kf_tangerang_001",
"machine": {
"machine_id": "kf_tangerang_001",
"name": "Tangerang Station Locker",
"location": "Tangerang Train Station, Main Hall",
"latitude": -6.1781,
"longitude": 106.6319,
"online": true
},
"cancelled_at": "2025-06-13T08:15:00Z",
"cancellation_party": "CANCELLATION_PARTY_DELIVERY_PLATFORM"
}
}

Event-Specific Fields:

FieldTypeRequiredDescription
reservation_idstringYesReservation identifier
machine_idstringYesMachine identifier
machineMachineYesMachine details
cancelled_attimestampYesWhen the cancellation occurred
cancellation_partyenumYesWho cancelled the reservation

Cancellation Party Values:

ValueDescription
CANCELLATION_PARTY_DELIVERY_PLATFORMCancelled by your platform via API
CANCELLATION_PARTY_KF_PLATFORMCancelled by KioskForce (e.g., expiry, maintenance)

5. Pickup Failed Event​

Triggered when a pickup attempt fails (door doesn't open).

{
"event_id": "evt_990e8400-e29b-41d4-a716-446655440004",
"idempotency_key": "idem_pickup_failed_abc123",
"event_time_unix_timestamp": 1731335400,
"event_time_timestamp": "2025-11-11T14:30:00Z",
"platform_order_id": "order_12345",
"reservation_id": "res_abc123",
"event_type": "EVENT_TYPE_PICKUP_FAILED",
"pick_up_failed_event": {
"source_type": "SOURCE_TYPE_MACHINE",
"cell": {
"description": "A03",
"cell_id": "cell_kf_tan_001_a03"
},
"machine": {
"machine_id": "kf_tangerang_001",
"name": "Tangerang Station Locker",
"location": "Tangerang Train Station, Main Hall",
"latitude": -6.1781,
"longitude": 106.6319,
"online": true
},
"error_message": "Door mechanism failure - maintenance required"
}
}

Note: Fields with empty or zero values are omitted. request_id only appears when initiated via API.

Event-Specific Fields:

FieldTypeRequiredDescription
source_typeSourceTypeYesHow the pickup was attempted
request_idstringNoX-Request-ID (only present if initiated via API)
cellCellYesCell where pickup failed
machineMachineYesMachine details
error_messagestringNoDetails about the failure (omitted if empty)

Use Cases:

  • Alert platform about failed pickup attempts for customer support
  • Track machine/door reliability and maintenance needs
  • Proactive customer communication about pickup issues
  • Detect potential fraud or tampering attempts
  • Trigger automated incident reports

6. OpenDoor Fulfilled Event​

Triggered when an out-of-band door opening request completes (via the OpenDoor API).

{
"event_id": "evt_aa0e8400-e29b-41d4-a716-446655440005",
"idempotency_key": "idem_opendoor_abc123",
"event_time_unix_timestamp": 1731509100,
"event_time_timestamp": "2025-11-13T15:45:00Z",
"platform_order_id": "order_12345",
"reservation_id": "res_abc123",
"event_type": "EVENT_TYPE_OPEN_DOOR_FULFILLED",
"open_door_fulfilled_event": {
"reservation_id": "res_abc123",
"request_id": "req_xyz789",
"cell": {
"description": "A03",
"cell_id": "cell_kf_tan_001_a03"
},
"success": true,
"fulfilled_at": "2025-11-13T15:45:00Z",
"reason": "Customer support request - customer unable to open door",
"platform_reference_id": "support_ticket_456",
"machine": {
"machine_id": "kf_tangerang_001",
"name": "Tangerang Station Locker",
"location": "Tangerang Train Station, Main Hall",
"latitude": -6.1781,
"longitude": 106.6319,
"online": true
}
}
}

Note: Fields with empty or zero values are omitted. error_message only appears when success = false.

Event-Specific Fields:

FieldTypeRequiredDescription
reservation_idstringYesReservation identifier
request_idstringYesX-Request-ID from original OpenDoor API call
cellCellYesCell that was opened (or attempted)
successbooleanYesWhether door opened successfully
fulfilled_attimestampYesWhen the operation completed
error_messagestringNoError details (only present if success = false)
reasonstringNoReason echoed from original request (omitted if empty)
platform_reference_idstringNoYour reference ID from original request (omitted if empty)
machineMachineYesMachine details

Use Cases:

  • Confirm door opening for customer support tickets
  • Track success rate of manual interventions
  • Alert support staff on repeated failures requiring physical intervention
  • Audit trail for all support-initiated door operations
  • Correlate support actions with customer complaints

Error Handling Example:

{
"event_id": "evt_bb0e8400-e29b-41d4-a716-446655440006",
"idempotency_key": "idem_opendoor_failed_abc123",
"event_time_unix_timestamp": 1731509100,
"event_time_timestamp": "2025-11-13T15:45:00Z",
"platform_order_id": "order_12345",
"reservation_id": "res_abc123",
"event_type": "EVENT_TYPE_OPEN_DOOR_FULFILLED",
"open_door_fulfilled_event": {
"reservation_id": "res_abc123",
"request_id": "req_xyz789",
"cell": {
"description": "A03",
"cell_id": "cell_kf_tan_001_a03"
},
"success": false,
"fulfilled_at": "2025-11-13T15:45:00Z",
"error_message": "Device offline - machine not responding",
"machine": {
"machine_id": "kf_tangerang_001",
"name": "Tangerang Station Locker",
"location": "Tangerang Train Station, Main Hall",
"latitude": -6.1781,
"longitude": 106.6319,
"online": false
}
}
}

---

## Webhook Timing: Dropoff vs Pickup

The timing of webhook events differs between dropoff and pickup operations:

| Event Type | Triggered When | State Transition | Reason |
|------------|----------------|------------------|--------|
| **Dropoff** | Door **closes** | `PENDING` → `DROPPED_OFF` | Ensures package was placed successfully |
| **Pickup** | Door **opens** | `DROPPED_OFF` → `PICKED_UP` | Consumer has accessed the package |

### Example Timeline: Dropoff

10:00:00 - API call /dropoffs → Door opens (state: PENDING, no webhook) 10:00:15 - Driver places package 10:00:30 - Door closes → State: DROPPED_OFF + Dropoff webhook sent


### Example Timeline: Pickup

12:00:00 - API call /pickups → Door opens → State: PICKED_UP + Pickup webhook sent immediately 12:00:15 - Consumer retrieves package 12:00:25 - Door closes


### Integration Considerations

1. **Don't assume immediate dropoff**: After calling `/dropoffs`, wait for the webhook before updating your order status to "delivered"
2. **Pickup is immediate**: The pickup webhook arrives as soon as the door opens successfully
3. **Grace periods**: Both operations allow door re-opening, but pickup state has already changed

## Webhook Authentication

The webhook system uses **HMAC-SHA256 signatures** to authenticate webhook payloads. This allows your platform to verify that webhook requests genuinely originate from KioskForce and haven't been tampered with.

### Signature Format

The signature is included in the `X-Webhook-Signature` header with the format:

```text
sha256=<hex-encoded-hash>

The signature is computed using:

  • Algorithm: HMAC-SHA256
  • Input: The raw JSON payload bytes
  • Secret: Your webhook API key (provided during platform onboarding)

Verifying Signatures​

Always verify the signature before processing webhook events. Here are examples in common languages:

Go​

import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
)

func verifyWebhookSignature(payload []byte, signature, secret string) bool {
// Remove "sha256=" prefix
expectedSig := strings.TrimPrefix(signature, "sha256=")

// Compute HMAC-SHA256
h := hmac.New(sha256.New, []byte(secret))
h.Write(payload)
computedSig := hex.EncodeToString(h.Sum(nil))

// Constant-time comparison to prevent timing attacks
return hmac.Equal([]byte(expectedSig), []byte(computedSig))
}

Python​

import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected_sig = signature.removeprefix("sha256=")
computed_sig = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_sig, computed_sig)

Node.js​

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expectedSig = signature.replace('sha256=', '');
const computedSig = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expectedSig),
Buffer.from(computedSig)
);
}

Webhook Headers​

Each webhook request includes these headers:

HeaderDescriptionExample
Content-TypeAlways JSONapplication/json
User-AgentService identifierdelivery-webhook/1.0
X-Platform-Order-IdYour original order IDorder_12345
X-Webhook-EventEvent type (proto enum)EVENT_TYPE_DROPOFF
X-Webhook-IDUnique event identifier (UUID)550e8400-e29b-41d4-a716-446655440000
X-Webhook-TimestampUnix timestamp (seconds)1737558150
X-Webhook-SignatureHMAC-SHA256 signaturesha256=a1b2c3d4...

X-Webhook-Event Values:

Event Type HeaderDescription
EVENT_TYPE_RESERVATION_CREATEDReservation created
EVENT_TYPE_DROPOFFPackage dropped off
EVENT_TYPE_PICKUPPackage picked up
EVENT_TYPE_RESERVATION_CANCELLEDReservation cancelled
EVENT_TYPE_PICKUP_FAILEDPickup attempt failed
EVENT_TYPE_OPEN_DOOR_FULFILLEDDoor open request completed

Example Request​

POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
User-Agent: delivery-webhook/1.0
X-Platform-Order-Id: order_12345
X-Webhook-Event: EVENT_TYPE_DROPOFF
X-Webhook-ID: 550e8400-e29b-41d4-a716-446655440000
X-Webhook-Timestamp: 1737558150
X-Webhook-Signature: sha256=a1b2c3d4e5f6...

Webhook Endpoint Requirements​

Your webhook endpoint must:

0. Use HTTPS​

Webhook URLs must use HTTPS for security. HTTP URLs will be rejected.

✅ Correct: https://yourapi.com/webhooks/delivery
❌ Invalid: http://yourapi.com/webhooks/delivery

1. Respond with HTTP 200​

Always return HTTP status 200 for successful processing.

// Express.js example
app.post('/webhooks/delivery', (req, res) => {
try {
const event = req.body;
console.log('Received webhook:', event);

// Process the event
processDeliveryEvent(event);

// Return 200 status
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(500).json({ error: 'Processing failed' });
}
});

2. Handle Duplicate Events​

Implement idempotency using the idempotency_key field from the payload (recommended). This key remains the same across retries for the same event:

const processedEvents = new Set();

function processDeliveryEvent(req) {
const payload = req.body;

// Use idempotency_key from payload (same across retries)
const idempotencyKey = payload.idempotency_key;

// Alternatively, use event_id for unique delivery tracking
// const eventId = payload.event_id;

if (processedEvents.has(idempotencyKey)) {
console.log('Duplicate event ignored:', idempotencyKey);
return;
}

processedEvents.add(idempotencyKey);

// Process the event based on event_type
switch (payload.event_type) {
case 'EVENT_TYPE_DROPOFF':
handleDropoff(payload.drop_off_event);
break;
case 'EVENT_TYPE_PICKUP':
handlePickup(payload.pick_up_event);
break;
case 'EVENT_TYPE_RESERVATION_CREATED':
handleReservationCreated(payload.reservation_created_event);
break;
case 'EVENT_TYPE_RESERVATION_CANCELLED':
handleReservationCancelled(payload.reservation_cancelled_event);
break;
case 'EVENT_TYPE_PICKUP_FAILED':
handlePickupFailed(payload.pick_up_failed_event);
break;
case 'EVENT_TYPE_OPEN_DOOR_FULFILLED':
handleOpenDoorFulfilled(payload.open_door_fulfilled_event);
break;
}
}

Key Fields for Idempotency:

FieldDescriptionUse Case
idempotency_keySame across retriesDeduplicate webhook processing
event_idUnique per delivery attemptTrack individual delivery attempts

3. Verify Signature (Required)​

Verify the X-Webhook-Signature header to ensure webhook authenticity and payload integrity:

const crypto = require('crypto');

app.post('/webhooks/delivery', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const payload = JSON.stringify(req.body);

// Verify signature
if (!verifyWebhookSignature(payload, signature, YOUR_WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}

// Reject old webhooks (optional but recommended)
const webhookAge = Math.floor(Date.now() / 1000) - parseInt(timestamp);
if (webhookAge > 300) { // 5 minutes
return res.status(401).json({ error: 'Webhook timestamp too old' });
}

// Process webhook...
});

function verifyWebhookSignature(payload, signature, secret) {
const expectedSig = signature.replace('sha256=', '');
const computedSig = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expectedSig),
Buffer.from(computedSig)
);
}

Retry Logic​

KioskForce will retry failed webhook deliveries with exponential backoff:

  • Max retries: 5 attempts
  • Backoff formula: min(30 × 2^retry_count, 3600) seconds
  • Dead letter queue: After max retries, events move to a failed queue for manual review
AttemptDelayCumulative Time
1Immediate0s
230s30s
360s~1.5 min
4120s~3.5 min
5240s~7.5 min

A webhook is considered failed if:

  • HTTP status is not 200-299
  • Request times out (30 seconds)
  • Connection cannot be established

Testing Webhooks​

1. Use a Webhook Testing Service​

For development, use services like:

# Install and run ngrok
npm install -g ngrok
ngrok http 3000

# Use the generated URL in your webhook_url
# Example: https://abc123.ngrok.io/webhooks/delivery

2. Test with Real Reservations​

Create a test reservation with your webhook URL:

curl -X POST "https://api.delivery.kioskforce.com/api/delivery/external/v1/reservations" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"machine_id": "test_machine",
"platform_order_id": "test_order_123",
"webhook_url": "https://abc123.ngrok.io/webhooks/delivery",
"packaging": {
"width": 300,
"height": 200,
"depth": 400
}
}'

Webhook Event Processing Examples​

Order Status Updates​

function handleDropoff(payload) {
const dropOffEvent = payload.drop_off_event;

// Update order status to "delivered"
updateOrderStatus(payload.platform_order_id, 'delivered');

// Send customer notification with cell info
sendCustomerNotification(payload.platform_order_id, {
message: `Your package has been delivered to locker ${dropOffEvent.cell.description}`,
machine_name: dropOffEvent.machine.name,
location: dropOffEvent.machine.location,
pickup_instructions: 'Use your pickup code to retrieve the package'
});
}

function handlePickup(payload) {
const pickUpEvent = payload.pick_up_event;

// Update order status to "completed"
updateOrderStatus(payload.platform_order_id, 'completed');

// Send completion notification
sendCustomerNotification(payload.platform_order_id, {
message: 'Thank you! Your package has been successfully picked up.'
});
}

function handleReservationCreated(payload) {
const reservationEvent = payload.reservation_created_event;

// Store pickup codes for customer
storePickupCodes(payload.platform_order_id, {
pickup_code: reservationEvent.pickup_code,
pickup_qrcode_url: reservationEvent.pickup_code_qrcode_url,
machine_name: reservationEvent.machine.name,
machine_location: reservationEvent.machine.location
});

// Send codes to customer
sendCustomerNotification(payload.platform_order_id, {
message: 'Your delivery locker is reserved!',
pickup_code: reservationEvent.pickup_code,
qrcode_url: reservationEvent.pickup_code_qrcode_url
});
}

Analytics Tracking​

function processDeliveryEvent(payload) {
// Track delivery metrics using payload wrapper fields
analytics.track('delivery_event', {
event_type: payload.event_type,
event_id: payload.event_id,
order_id: payload.platform_order_id,
reservation_id: payload.reservation_id,
timestamp: payload.event_time_timestamp,
unix_timestamp: payload.event_time_unix_timestamp
});

// Calculate delivery times
if (payload.event_type === 'EVENT_TYPE_PICKUP') {
calculateDeliveryTime(payload.reservation_id);
}
}

Troubleshooting​

Webhook Not Received​

  1. Check your endpoint is publicly accessible
  2. Verify HTTPS is properly configured
  3. Ensure your server responds with HTTP 200
  4. Check webhook URL in reservation request

Duplicate Events​

  • Implement idempotency using idempotency_key from the payload body (same across retries)
  • Store processed idempotency keys in database/cache with TTL
  • Use event_id to track individual delivery attempts

Missing Events​

  • Check server logs for errors
  • Verify webhook endpoint uptime
  • Contact KioskForce support for webhook delivery logs

Security Best Practices​

  1. Use HTTPS: Always use HTTPS URLs for webhook endpoints (required)
  2. Verify Signatures: Always verify X-Webhook-Signature before processing any webhook
  3. Check Timestamps: Reject webhooks with old X-Webhook-Timestamp header or event_time_unix_timestamp payload field (e.g., > 5 minutes) to prevent replay attacks
  4. Use Constant-Time Comparison: When verifying signatures, use constant-time comparison functions to prevent timing attacks
  5. Idempotency: Use idempotency_key from the payload to handle duplicate deliveries gracefully (same key across retries)
  6. Rotate Keys: Update your webhook API key periodically
  7. Validate Input: Sanitize and validate all webhook data
  8. Rate Limiting: Implement rate limiting on webhook endpoints
  9. Log Events: Log all webhook events with event_id and idempotency_key for debugging and audit

Support​

For webhook-related issues:

  • 📧 Email: api-support@kioskforce.com
  • 📖 Include: Webhook URL, event type, and timestamps
  • 🔍 Check: Server logs and network connectivity

Webhook integration enables real-time order tracking and provides the best customer experience with your delivery locker integration!