API Reference

WEBHOOK

The Webhook system allows external services to receive real-time updates whenever specific events occur within the Kivly platform. This enables seamless automation and synchronization across systems without the need for polling.

Webhook Documentation - Kivly Backend

General Description

Kivly's webhook system allows you to receive real-time notifications when specific events occur in your application. This allows you to keep your external systems synchronized with Kivly data.

Webhook Configuration

To configure webhooks, you must set the webhook_url and webhook_secret fields in your user profile.

Configuration Fields

FieldTypeRequiredDescription
webhook_urlstringYesURL where webhooks will be sent
webhook_secretstringYesSecret to verify authenticity

Configuration Example via GraphQL

mutation {
  userMutation {
    updateUser(inputData: {
      webhookUrl: "https://your-domain.com/webhooks/kivly"
      webhookSecret: "your_super_secure_secret_here"
    }) {
      success
      message
    }
  }
}

Available Events

Customer Events

  • customer.created - Customer created
  • customer.updated - Customer updated
  • customer.deleted - Customer deleted

Establishment Events

  • establishment.created - Establishment created
  • establishment.updated - Establishment updated
  • establishment.deleted - Establishment deleted

Point Transaction Events

  • point_transaction.created - Point transaction created
  • point_transaction.deleted - Point transaction deleted

Redemption Events

  • redemption.created - Redemption created
  • redemption.redeemed - Redemption redeemed
  • redemption.cancelled - Redemption cancelled
  • redemption.failed - Redemption failed

Reward Events

  • reward.created - Reward created
  • reward.updated - Reward updated
  • reward.deleted - Reward deleted
  • reward.expired - Reward expired

Payload Structure

Headers

Content-Type: application/json
User-Agent: Kivly-Webhook/1.0
X-Kivly-Event: customer.created
X-Kivly-Signature: sha256=5d41402abc4b2a76b9719d911017c592
X-Kivly-Timestamp: 1640995200

Body

{
  "id": "unique-event-id",
  "event": "customer.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "id": "customer_id",
    "name": "Juan Pérez",
    "email": "[email protected]",
    "phone": "+34600123456",
    "owner": "user_id",
    "establishment": "establishment_id",
    "loyalty_points": 0,
    "is_active": true,
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:30:00Z"
  },
  "owner_id": "user_id",
  "establishment_id": "establishment_id",
  "customer_id": "customer_id",
  "metadata": {
    "entity": "customer",
    "action": "created"
  }
}

Signature Verification

To verify that the webhook comes from Kivly, you must validate the HMAC-SHA256 signature:

Python Example

import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    expected_signature = f"sha256={expected_signature}"
    return hmac.compare_digest(expected_signature, signature)

# Usage
payload = request.body  # Raw JSON string
signature = request.headers.get('X-Kivly-Signature')
secret = 'your_super_secure_secret_here'

if verify_webhook_signature(payload, signature, secret):
    # Valid webhook
    process_webhook(payload)
else:
    # Invalid webhook
    return 401

Node.js Example

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payload, 'utf8')
        .digest('hex');

    const expectedSignatureWithPrefix = `sha256=${expectedSignature}`;

    return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignatureWithPrefix)
    );
}

API Endpoints

Get Available Events

GET /api/v1/webhooks/events
Authorization: Bearer {token}

Response:

{
  "events": {
    "customer": [
      "customer.created",
      "customer.updated",
      "customer.deleted"
    ],
    "establishment": [
      "establishment.created",
      "establishment.updated",
      "establishment.deleted"
    ],
    "point_transaction": [
      "point_transaction.created",
      "point_transaction.deleted"
    ],
    "redemption": [
      "redemption.created",
      "redemption.redeemed",
      "redemption.cancelled",
      "redemption.failed"
    ],
    "reward": [
      "reward.created",
      "reward.updated",
      "reward.deleted",
      "reward.expired"
    ]
  }
}

Test Webhook

POST /api/v1/webhooks/test
Authorization: Bearer {token}
Content-Type: application/json

{
  "url": "https://your-domain.com/webhooks/kivly",
  "secret": "your_super_secure_secret_here"
}

Response:

{
  "success": true,
  "message": "Webhook configured correctly. Test event sent successfully."
}

GraphQL Queries

Get Available Events

query {
  webhookQuery {
    webhookEvents {
      entity
      events
    }
  }
}

Get Current Configuration

query {
  webhookQuery {
    webhookConfig {
      webhookUrl
      webhookSecret
      hasWebhookConfigured
    }
  }
}

Test Webhook

mutation {
  webhookMutation {
    testWebhook(webhookInput: {
      url: "https://your-domain.com/webhooks/kivly"
      secret: "your_super_secure_secret_here"
    }) {
      success
      message
    }
  }
}

Event Payload Examples

Customer Created

{
  "id": "evt_12345",
  "event": "customer.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "id": "cust_67890",
    "name": "María García",
    "email": "[email protected]",
    "phone": "+34600987654",
    "owner": "user_123",
    "establishment": "est_456",
    "loyalty_points": 0,
    "is_active": true,
    "created_at": "2024-01-15T10:30:00Z"
  },
  "owner_id": "user_123",
  "establishment_id": "est_456",
  "customer_id": "cust_67890"
}

Point Transaction Created

{
  "id": "evt_54321",
  "event": "point_transaction.created",
  "timestamp": "2024-01-15T11:00:00Z",
  "data": {
    "id": "tx_98765",
    "customer_id": "cust_67890",
    "amount": 100,
    "type": "earn",
    "point_type": "point",
    "description": "Store purchase",
    "owner": "user_123",
    "created_at": "2024-01-15T11:00:00Z"
  },
  "owner_id": "user_123",
  "establishment_id": "est_456",
  "customer_id": "cust_67890"
}

Redemption Redeemed

{
  "id": "evt_11111",
  "event": "redemption.redeemed",
  "timestamp": "2024-01-15T12:00:00Z",
  "data": {
    "id": "red_22222",
    "customer_id": "cust_67890",
    "reward_id": "rew_33333",
    "points_required": 500,
    "status": "redeemed",
    "owner": "user_123",
    "redeemed_at": "2024-01-15T12:00:00Z"
  },
  "owner_id": "user_123",
  "establishment_id": "est_456",
  "customer_id": "cust_67890"
}

Error Handling and Retries

  • Webhooks will be automatically retried up to 3 times
  • The interval between retries is 60 seconds
  • Any response with HTTP 2xx status code is considered successful
  • Webhooks fail permanently after 3 attempts

Timeout

  • Default timeout: 30 seconds
  • If your endpoint doesn't respond within 30 seconds, it will be considered failed

Best Practices

  1. Always verify signature: Never process webhooks without verifying the HMAC signature
  2. Idempotency: Process each event only once using the id field
  3. Quick response: Respond with 200 OK as soon as possible
  4. Asynchronous processing: If you need heavy processing, do it in the background
  5. Logs: Keep detailed logs of all received webhooks
  6. Error handling: Implement robust error handling

Complete Implementation Example

Flask (Python)

from flask import Flask, request, jsonify
import hmac
import hashlib
import json

app = Flask(__name__)
WEBHOOK_SECRET = 'your_super_secure_secret_here'

def verify_signature(payload, signature):
    expected = hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

@app.route('/webhooks/kivly', methods=['POST'])
def handle_webhook():
    payload = request.get_data()
    signature = request.headers.get('X-Kivly-Signature')

    if not verify_signature(payload, signature):
        return jsonify({'error': 'Invalid signature'}), 401

    event_data = request.get_json()
    event_type = event_data['event']

    # Process event
    if event_type == 'customer.created':
        handle_customer_created(event_data)
    elif event_type == 'point_transaction.created':
        handle_point_transaction_created(event_data)
    # ... more events

    return jsonify({'status': 'success'}), 200

def handle_customer_created(event_data):
    customer = event_data['data']
    print(f"New customer: {customer['name']} ({customer['email']})")
    # Your logic here

if __name__ == '__main__':
    app.run(port=5000)

Express.js (Node.js)

const express = require('express');
const crypto = require('crypto');
const app = express();

const WEBHOOK_SECRET = 'your_super_secure_secret_here';

app.use('/webhooks/kivly', express.raw({type: 'application/json'}));

function verifySignature(payload, signature) {
    const expected = crypto
        .createHmac('sha256', WEBHOOK_SECRET)
        .update(payload)
        .digest('hex');

    return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(`sha256=${expected}`)
    );
}

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

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

    const eventData = JSON.parse(payload);
    const eventType = eventData.event;

    // Process event
    switch(eventType) {
        case 'customer.created':
            handleCustomerCreated(eventData);
            break;
        case 'point_transaction.created':
            handlePointTransactionCreated(eventData);
            break;
        // ... more events
    }

    res.json({status: 'success'});
});

function handleCustomerCreated(eventData) {
    const customer = eventData.data;
    console.log(`New customer: ${customer.name} (${customer.email})`);
    // Your logic here
}

app.listen(3000, () => {
    console.log('Webhook server running on port 3000');
});

OpenAPI Specification

openapi: 3.0.0
info:
  title: Kivly Webhooks API
  description: API to configure and test webhooks in Kivly
  version: 1.0.0
servers:
  - url: https://api.kivly.com/api/v1
    description: Production server

paths:
  /webhooks/events:
    get:
      summary: Get available events
      description: Returns all available webhook events organized by entity
      security:
        - bearerAuth: []
      responses:
        '200':
          description: List of available events
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        type: string
                example:
                  events:
                    customer: ["customer.created", "customer.updated", "customer.deleted"]
                    establishment: ["establishment.created", "establishment.updated", "establishment.deleted"]

  /webhooks/test:
    post:
      summary: Test webhook configuration
      description: Sends a test event to the configured webhook
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
                - secret
              properties:
                url:
                  type: string
                  format: uri
                  description: Webhook URL to test
                secret:
                  type: string
                  description: Secret to verify signature
              example:
                url: "https://your-domain.com/webhooks/kivly"
                secret: "your_super_secure_secret_here"
      responses:
        '200':
          description: Test result
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                example:
                  success: true
                  message: "Webhook configured correctly. Test event sent successfully."

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    WebhookPayload:
      type: object
      properties:
        id:
          type: string
          description: Unique event ID
        event:
          type: string
          description: Event type
          enum:
            - customer.created
            - customer.updated
            - customer.deleted
            - establishment.created
            - establishment.updated
            - establishment.deleted
            - point_transaction.created
            - point_transaction.updated
            - point_transaction.deleted
            - redemption.created
            - redemption.redeemed
            - redemption.cancelled
            - redemption.failed
            - reward.created
            - reward.updated
            - reward.deleted
            - reward.expired
        timestamp:
          type: string
          format: date-time
          description: Event timestamp
        data:
          type: object
          description: Event data (varies by type)
        owner_id:
          type: string
          description: Owner ID
        establishment_id:
          type: string
          description: Establishment ID (optional)
          nullable: true
        customer_id:
          type: string
          description: Customer ID (optional)
          nullable: true
        metadata:
          type: object
          description: Additional metadata
          nullable: true
      required:
        - id
        - event
        - timestamp
        - data
        - owner_id