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!