# Integration Implementation Examples

{% tabs %}
{% tab title="Javascript" %}

```go
const express = require('express');
const crypto = require('crypto');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 8080;

const SIGNING_SECRET = process.env.SIGNING_SECRET;
const TOLERANCE_PERIOD = 5 * 60 * 1000; // 5 minutes in milliseconds

// Middleware to preserve raw body for signature verification
const rawBodySaver = function (req, res, buf, encoding) {
    if (buf && buf.length) {
        req.rawBody = buf.toString(encoding || 'utf8');
    }
}

// Function to calculate HMAC signature
function calculateHMAC(payloadWithTimestamp) {
    const hmac = crypto.createHmac('sha256', SIGNING_SECRET);
    hmac.update(payloadWithTimestamp);
    return hmac.digest('base64');
}

// Middleware to verify webhook signature and prevent replay attacks
function verifyWebhookSignature(req, res, next) {
    const timestamp = req.headers['paynow-timestamp'];
    const providedSignature = req.headers['paynow-signature'];

    if (!timestamp || !providedSignature) {
        return res.status(400).send('Missing required headers');
    }

    const timestampInt = parseInt(timestamp, 10);
    if (isNaN(timestampInt)) {
        return res.status(400).send('Invalid timestamp format');
    }

    const timestampTime = new Date(timestampInt);
    const currentTime = new Date();
    if (currentTime - timestampTime > TOLERANCE_PERIOD) {
        return res.status(401).send('Timestamp out of tolerance');
    }

    const payloadWithTimestamp = `${timestamp}.${req.rawBody}`;
    const expectedSignature = calculateHMAC(payloadWithTimestamp);

    const signatureValid = crypto.timingSafeEqual(Buffer.from(providedSignature, 'base64'), Buffer.from(expectedSignature, 'base64'));

    if (!signatureValid) {
        return res.status(401).send('Invalid signature');
    }

    next();
}

app.use(express.json({ verify: rawBodySaver }));

app.post('/webhook', verifyWebhookSignature, (req, res) => {
    const eventType = req.body.event_type;
    switch (eventType) {
        case 'ON_DELIVERY_ITEM_ADDED':
            // handleOnDeliveryItemAdded(req.body);
            break;
        case 'ON_DELIVERY_ITEM_ACTIVATED':
            // handleOnDeliveryItemActivated(req.body);
            break;
        case 'ON_DELIVERY_ITEM_USED':
            // handleOnDeliveryItemUsed(req.body);
            break;
        case 'ON_DELIVERY_ITEM_REVOKED':
            // handleOnDeliveryItemRevoked(req.body);
            break;
        default:
            console.log(`Received unknown event type: ${eventType}`);
    }
    res.status(200).send('Webhook processed');
});

app.listen(PORT, () => {
    console.log(`Server running at http://localhost:${PORT}/`);
});
```

{% endtab %}

{% tab title="GO" %}

```go
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "strconv"
    "time"
)

const tolerancePeriod = 5 * time.Minute

func getSigningSecret() []byte {
    return []byte(os.Getenv("SIGNING_SECRET"))
}

func calculateHMAC(payloadWithTimestamp string) string {
    h := hmac.New(sha256.New, getSigningSecret())
    h.Write([]byte(payloadWithTimestamp))
    return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

func verifyWebhookSignature(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        timestamp := r.Header.Get("paynow-timestamp")
        providedSignature := r.Header.Get("paynow-signature")

        if timestamp == "" || providedSignature == "" {
            http.Error(w, "Missing required headers", http.StatusBadRequest)
            return
        }

        timestampInt, err := strconv.ParseInt(timestamp, 10, 64)
        if err != nil {
            http.Error(w, "Invalid timestamp format", http.StatusBadRequest)
            return
        }

        timestampTime := time.Unix(timestampInt/1000, (timestampInt%1000)*int64(time.Millisecond))
        currentTime := time.Now()

        if currentTime.Sub(timestampTime) > tolerancePeriod {
            http.Error(w, "Timestamp out of tolerance", http.StatusUnauthorized)
            return
        }

        body, err := ioutil.ReadAll(r.Body)
        if err != nil {
            http.Error(w, "Failed to read request body", http.StatusInternalServerError)
            return
        }
        defer r.Body.Close()

        payloadWithTimestamp := fmt.Sprintf("%d.%s", timestampInt, body)
        expectedSignature := calculateHMAC(payloadWithTimestamp)

        if !hmac.Equal([]byte(providedSignature), []byte(expectedSignature)) {
            http.Error(w, "Invalid signature", http.StatusUnauthorized)
            return
        }

        next(w, r)
    }
}

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    var data struct {
        EventType string `json:"event_type"`
    }
    if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
        http.Error(w, "Error decoding JSON", http.StatusBadRequest)
        return
    }

    fmt.Printf("Received event type: %s\n", data.EventType)
    // Process event based on data.EventType here
    fmt.Fprintln(w, "Webhook processed")
}

func main() {
    http.HandleFunc("/webhook", verifyWebhookSignature(handleWebhook))
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    log.Printf("Server running at http://localhost:%s/\n", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.paynow.gg/webhooks/integration-implementation-examples.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
