CHP / 1.0
CRAABS Honeypot Protocol
Open integration protocol to connect any honeypot to the CRAABS CTI platform.
Events are automatically converted to enriched IOCs shared with the community.
This is the implementation reference for developers writing a CHP connector: every endpoint, field, constraint and error code. For what honeypots are, how to install a ready-made node and how the rest of the platform works, start with the general documentation.

1. Overview

CHP (CRAABS Honeypot Protocol) allows any honeypot to forward events to CRAABS via a simple REST API. For each event received, the platform:

  • Creates or enriches an IOC from the attacker IP
  • Calculates a malicious score based on event type and frequency
  • Maps automatically to the corresponding MITRE ATT&CK techniques
  • Geolocates the source IP
  • Triggers alerts if the score is critical
Your honeypot must first be registered by a platform administrator. You will receive a unique CHP key.

2. Getting a CHP key

Before writing any code you need a node key. The flow is deliberately manual, because a key grants write access to shared community intelligence.

  1. Sign in with a Developer or Pro account and open SOC > Honeypot Network.
  2. Register a new honeypot: give it a name, a type and a location. The location is a label for you, not a geolocation.
  3. Copy the generated chp_... key. It is shown in full only once.
  4. Store it as an environment variable on the sensor, never in the source tree.
  5. Send a first heartbeat. The node stays pending until an administrator approves it, and events sent before approval are rejected with 403.
Test your connector against the ping endpoint first. It exercises authentication and network path without writing anything into the community database, so a misconfigured client cannot pollute the shared dataset while you debug it.

3. Authentication

Every request must include the CHP key in the X-Honeypot-Key header.

X-Honeypot-Key: chp_a1b2c3d4e5f6...

The key is prefixed with chp_ and is 60 characters long. Never log it in plaintext or commit it to source control.

For clients that cannot set a custom header, the key is also accepted as a bearer token:

Authorization: Bearer chp_a1b2c3d4e5f6...
HTTPS only. The key authenticates your node and the payloads carry captured credentials. Never send CHP traffic over plain HTTP, and never reuse one key across several nodes: if it leaks, you cannot tell which sensor is compromised. Regenerate a key from the Honeypot Network page at the first suspicion; the old one stops working immediately.

4. Endpoints

GET/api/honeypot/ping

Heartbeat. Signals that the node is active. Call every 5 minutes.

curl -H "X-Honeypot-Key: chp_..." https://cti.craabs.com/api/honeypot/ping

# Response 200
{"status": "ok", "node": "SSH-Trap", "accepted": true, "protocol": "CHP/1.0"}

POST/api/honeypot/event

Sends a single event. Use this for real-time forwarding.

POST/api/honeypot/events/bulk

Sends up to 100 events in one request. Recommended for high-volume sensors.

# Body: {"events": [ {event}, {event}, ... ]}
# Response 201: {"accepted": 95, "rejected": 2, "iocs_created": 88}

GET/api/honeypot/status

Returns statistics for your node: event count, IOCs generated, last seen.

5. Event format

FieldTypeRequiredDescriptionValues
source_ipstringRequiredAttacker IP addressValid IPv4
honeypot_typestringOptionalLured service typessh, http, ftp, rdp, smtp, telnet, mysql, vnc, sip, modbus, generic, custom
event_typestringOptionalEvent naturelogin_attempt, brute_force, exploit, payload, scan, connect, malware, command, generic
timestampstringOptionalISO 8601 timestamp2026-05-14T10:30:00Z
source_portintegerOptionalAttacker source port1-65535
dest_portintegerOptionalDestination port22, 80, 443...
usernamestringOptionalAttempted usernamemax 200 chars
passwordstringOptionalAttempted passwordmax 500 chars
payloadstringOptionalRaw payload (base64 recommended)free text
severitystringOptionalEstimated severitylow, medium, high, critical
tagsarrayOptionalAdditional tags["mirai", "scanner"]

Unknown enum values are replaced, not rejected

If honeypot_type, event_type or severity holds a value outside the list above, the request still succeeds but the field is silently rewritten to custom, generic and medium respectively. This keeps a misconfigured sensor from losing data, but it also means a typo will not raise an error. Check the values you send: "ssh2" is stored as custom, not as an error.

Response to a single event

{
  "status":    "accepted",
  "event_id":  42,
  "ioc_id":    1337,
  "ioc_value": "185.220.101.47",
  "score":     65,
  "protocol":  "CHP/1.0"
}

ioc_id, ioc_value and score are null when the event did not produce an indicator. The bulk endpoint returns counters instead: {"accepted": 98, "rejected": 2, "iocs_created": 41}.

What the platform does with your event

Each accepted event goes through the same pipeline, so your sensor does not have to enrich anything itself:

  • Deduplication. If the source IP already exists as an indicator, the existing record is reused rather than duplicated, and its score is raised.
  • Scoring. A base score is derived from the event type, then increased when the same address is seen repeatedly, so persistent attackers rise above one-off scanners.
  • Geolocation. Country, city, ASN and ISP are resolved automatically. Sending country yourself is optional and only used as a hint.
  • Sharing. The resulting indicator becomes available to the community, to the REST API and to SIEM feeds.
On captured credentials. The username and password fields are stored to support pattern analysis across attackers. Send only what your decoy actually captured. Never forward credentials from a real system, and never send payloads containing personal data: this is shared community intelligence, not a private log store.

6. Code examples

Python

import requests
from datetime import datetime, timezone

URL = "https://cti.craabs.com"
KEY = "chp_your_key_here"
HEADERS = {"X-Honeypot-Key": KEY, "Content-Type": "application/json"}

def send_event(source_ip, honeypot_type, event_type, **kwargs):
    payload = {
        "source_ip": source_ip,
        "honeypot_type": honeypot_type,
        "event_type": event_type,
        "timestamp": datetime.now(timezone.utc).isoformat(),
        **kwargs
    }
    r = requests.post(f"{URL}/api/honeypot/event", headers=HEADERS, json=payload, timeout=5)
    return r.json()

# Heartbeat (call every 5 minutes)
def heartbeat():
    requests.get(f"{URL}/api/honeypot/ping", headers=HEADERS, timeout=5)

# Example: SSH login attempt
result = send_event(
    source_ip="185.220.101.47",
    honeypot_type="ssh",
    event_type="login_attempt",
    dest_port=22,
    username="root",
    password="123456",
    severity="medium",
    tags=["brute_force"],
)
print(f"IOC #{result['ioc_id']} - score: {result['score']}")

Bash / curl

#!/bin/bash
URL="https://cti.craabs.com"
KEY="chp_your_key_here"

# Heartbeat
curl -s -H "X-Honeypot-Key: $KEY" "$URL/api/honeypot/ping"

# Send event
curl -s -X POST \
  -H "X-Honeypot-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source_ip": "185.220.101.47",
    "honeypot_type": "ssh",
    "event_type": "login_attempt",
    "dest_port": 22,
    "username": "admin",
    "severity": "medium"
  }' \
  "$URL/api/honeypot/event"

Node.js

const axios = require('axios');

const client = axios.create({
  baseURL: 'https://cti.craabs.com',
  headers: { 'X-Honeypot-Key': 'chp_your_key_here' },
  timeout: 5000,
});

async function sendEvent(event) {
  const res = await client.post('/api/honeypot/event', {
    ...event,
    timestamp: new Date().toISOString(),
  });
  return res.data;
}

// Heartbeat every 5 minutes
setInterval(() => client.get('/api/honeypot/ping'), 300_000);

// Example
sendEvent({
  source_ip: '185.220.101.47',
  honeypot_type: 'http',
  event_type: 'scan',
  dest_port: 80,
  severity: 'low',
}).then(r => console.log('IOC:', r.ioc_id));

Go

package main

import (
    "bytes"; "encoding/json"; "net/http"; "time"
)

const URL = "https://cti.craabs.com"
const KEY = "chp_your_key_here"

type Event struct {
    SourceIP     string   `json:"source_ip"`
    HoneypotType string   `json:"honeypot_type"`
    EventType    string   `json:"event_type"`
    Timestamp    string   `json:"timestamp"`
    DestPort     int      `json:"dest_port,omitempty"`
    Severity     string   `json:"severity"`
    Tags         []string `json:"tags,omitempty"`
}

func SendEvent(e Event) error {
    e.Timestamp = time.Now().UTC().Format(time.RFC3339)
    body, _ := json.Marshal(e)
    req, _ := http.NewRequest("POST", URL+"/api/honeypot/event", bytes.NewBuffer(body))
    req.Header.Set("X-Honeypot-Key", KEY)
    req.Header.Set("Content-Type", "application/json")
    _, err := http.DefaultClient.Do(req)
    return err
}

Cowrie SSH Honeypot integration

# cowrie_to_craabs.py - tail Cowrie JSON log and forward to CRAABS
import json, time, requests

KEY = "chp_your_key_here"
LOG = "/home/cowrie/var/log/cowrie/cowrie.json"
HEADERS = {"X-Honeypot-Key": KEY}

EVENT_MAP = {
    "cowrie.login.failed":         "login_attempt",
    "cowrie.login.success":        "login_success",
    "cowrie.command.input":        "command",
    "cowrie.session.file_download":"payload",
}

def tail_log():
    with open(LOG) as f:
        f.seek(0, 2)
        while True:
            line = f.readline()
            if line:
                yield json.loads(line.strip())
            else:
                time.sleep(0.5)

for entry in tail_log():
    if not entry.get("src_ip"):
        continue
    requests.post("https://cti.craabs.com/api/honeypot/event",
        headers=HEADERS, timeout=5,
        json={
            "source_ip":     entry["src_ip"],
            "honeypot_type": "ssh",
            "event_type":    EVENT_MAP.get(entry.get("eventid"), "generic"),
            "dest_port":     22,
            "username":      entry.get("username"),
            "password":      entry.get("password"),
            "payload":       entry.get("input"),
            "timestamp":     entry.get("timestamp"),
            "severity":      "high" if "success" in entry.get("eventid","") else "medium",
            "tags":          ["cowrie", "ssh"],
        }
    )

7. Error codes

HTTP codeMeaningAction
201Event acceptedContinue normally
400Invalid JSON or missing source_ipFix the payload
401Missing or invalid CHP keyCheck X-Honeypot-Key
403Node not approvedContact the platform administrator
429Rate limit exceededBack off, switch to bulk mode, retry after 60s
500Server errorRetry in 30s

Errors are returned as JSON with an error field describing the problem, for example {"error": "source_ip is required"}. A 403 specifically means the node exists but is still pending administrator approval: the key is valid, so do not regenerate it, simply wait for approval.

Treat 429 as a contract even if you rarely see it. Throttling may be tightened as the network grows, and a client that already backs off correctly will keep working unchanged. A sensor that assumes unlimited throughput will not.

8. Best practices

  • Send the heartbeat every 5 minutes so the platform can detect if your node goes offline.
  • Use bulk mode if your honeypot generates more than 10 events per minute.
  • Never hardcode the CHP key in public code. Use environment variables.
  • Handle 429 and 500 errors with exponential backoff: wait 5s, then 15s, then 60s.
  • Include the actual event timestamp, not the send time.
  • For exploits and payloads, encode in base64 to avoid JSON encoding issues.
  • Use critical severity only for successful exploits or confirmed malware downloads.
  • Buffer to disk, not to memory. If the platform is unreachable, a sensor that queues in RAM loses everything on restart, which is exactly when an attacker is most active.
  • Do not retry a 400 or a 401. These mean your payload or key is wrong; retrying the same request forever only fills your logs. Retry 429 and 500 only.
  • Cap your own send rate. A honeypot under a volumetric attack can generate thousands of events per second. Aggregate or sample before sending, otherwise your sensor becomes the noise it was meant to detect.

9. Limits and versioning

ConstraintValue
Events per bulk request100 maximum
Heartbeat interval5 minutes recommended
username field200 characters
password field500 characters
tags field500 characters once joined
TransportHTTPS only
Protocol versionCHP/1.0, returned in every response

Values longer than the limits above are truncated rather than rejected, so an oversized payload will not cost you the event, but it will cost you the tail of the data.

The protocol follows additive versioning: new optional fields may appear in a minor release, but existing field names and semantics will not change without a major version bump. Parse responses tolerantly, ignoring unknown keys, and your connector will keep working across upgrades.

To register your honeypot and obtain a CHP key, contact your CRAABS administrator or visit the Honeypot Network page. See also the CHP overview in the general documentation for the event lifecycle diagram and deployment guidance.