Xexle HashCheck
Starter code for integrations

Libraries, code examples and AI instruction

These examples calculate SHA-256 hashes locally, then send only hash strings to HashCheck. File contents are not uploaded. You can also copy the AI Agent Instruction into your coding assistant.

AI Agent Instruction

Copy this instruction into your AI coding agent when you want it to integrate HashCheck into your project.

You are integrating Xexle HashCheck into this project.

Goal:
- Calculate file hashes locally.
- Send only hash strings to HashCheck.
- Never upload file contents to HashCheck.
- Add clear functions that fit the host project style and language.

Hash algorithm:
- Prefer SHA-256 for new code.
- SHA-256 output is 64 lowercase hexadecimal characters.
- Hash the exact file bytes in streaming/chunked mode so large files do not load fully into memory.
- Normalize generated hashes to lowercase.
- Validate input hashes before sending them. HashCheck accepts lowercase/uppercase hex strings with length 32, 40, 64 or 128, but generated hashes should use SHA-256 unless the user explicitly requests another algorithm.

Required functions:
- hashFile(filePath): return the SHA-256 hash of one file.
- hashDirectory(directoryPath): recursively calculate hashes for files in a directory. Return a map/list that preserves file path to hash.
- checkHashes(hashes): send hashes to the HashCheck lookup API and return parsed JSON.
- checkFile(filePath): convenience wrapper around hashFile + checkHashes.
- checkDirectory(directoryPath): convenience wrapper around hashDirectory + batched checkHashes.

HashCheck API:
- Lookup endpoint: https://hashcheck.xexle.com/api/check
- Add endpoint: https://hashcheck.xexle.com/api/add
- Renew validator endpoint: https://hashcheck.xexle.com/api/renew-validator
- Send requests as GET or POST form fields.
- Parameter: hashes=hash1,hash2,hash3
- Default lookup returns only found hashes.
- Use include_missing=1 when the project needs explicit 0 values for hashes not found.
- Response shape: { "status": true, "data": { "hash": code }, "message": "optional" }

Lookup result codes:
- 0: hash not found in HashCheck database. Returned only when include_missing=1 or found_only=0 is used.
- 1: hash is present in Xexle service list.
- 2: hash was submitted by a trusted validator.
- 3: hash was submitted by a user and is not verified.

Limits and batching:
- Lookup limit: up to 1000 valid hashes per request.
- Batch directory checks into chunks of at most 1000 hashes.
- Prefer batching over sending one request per file.
- If a request fails or returns a message about cooldown/rate limiting, surface that message to the caller and do not spin in a tight retry loop.
- Use reasonable HTTP timeouts.

Adding hashes:
- Anonymous online/user additions are limited to 3 valid hashes per request and one request every 15 minutes per IP.
- Programmatic/API additions should use a trusted validator token.
- Validator add endpoint: https://hashcheck.xexle.com/api/add?hashes=hash1,hash2&validator=VALIDATOR_TOKEN
- Validator renew endpoint: https://hashcheck.xexle.com/api/renew-validator?validator=OLD_VALIDATOR_TOKEN
- Renewing a validator token removes the old validator token and returns a new one for the same validator record.
- Keep validator tokens only on trusted server-side systems.
- Never put validator tokens into browser JavaScript, mobile app bundles, public repositories, logs, screenshots or client-side config.
- If there is no validator token, do not build bulk add automation. Direct the user to the online Add page instead.

Security and compliance behavior:
- Do not claim that a hash match proves a file is illegal or criminal.
- Treat results as technical signals.
- Code 3 is user-submitted and unverified; do not treat it as a verified blocklist.
- Do not send file names, file contents or unnecessary personal data to HashCheck unless the user explicitly asks and there is a lawful reason.
- If the host project logs API calls, avoid logging validator tokens and avoid logging sensitive file paths by default.

Implementation quality:
- Follow the existing project coding style.
- Use standard libraries for hashing and HTTP where practical.
- Stream file reads.
- Deduplicate hashes before sending.
- Preserve mapping from file path to hash and from hash to result code.
- Return structured errors instead of throwing raw HTTP details into UI.
- Add small tests or examples for hashFile, hashDirectory and checkHashes when the project has a test framework.
- Document the returned codes near the integration point.

Python

import hashlib
import json
import os
import urllib.parse
import urllib.request

API_URL = "https://hashcheck.xexle.com/api/check"

def hash_file(path):
    digest = hashlib.sha256()
    with open(path, "rb") as file:
        for chunk in iter(lambda: file.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()

def hash_directory(path):
    hashes = {}
    for root, _, files in os.walk(path):
        for name in sorted(files):
            file_path = os.path.join(root, name)
            if os.path.isfile(file_path):
                hashes[file_path] = hash_file(file_path)
    return hashes

def check_hashes(hashes):
    values = ",".join(hashes)
    url = API_URL + "?" + urllib.parse.urlencode({"hashes": values})
    with urllib.request.urlopen(url, timeout=30) as response:
        return json.loads(response.read().decode("utf-8"))

if __name__ == "__main__":
    file_hash = hash_file("example.bin")
    print(file_hash)
    print(check_hashes([file_hash]))

Node.js

import { createHash } from "node:crypto";
import { createReadStream } from "node:fs";
import { readdir } from "node:fs/promises";
import path from "node:path";

const API_URL = "https://hashcheck.xexle.com/api/check";

export function hashFile(filePath) {
  return new Promise((resolve, reject) => {
    const hash = createHash("sha256");
    const stream = createReadStream(filePath);
    stream.on("data", chunk => hash.update(chunk));
    stream.on("error", reject);
    stream.on("end", () => resolve(hash.digest("hex")));
  });
}

export async function hashDirectory(dirPath) {
  const result = {};
  const entries = await readdir(dirPath, { withFileTypes: true });

  for (const entry of entries) {
    const fullPath = path.join(dirPath, entry.name);
    if (entry.isDirectory()) {
      Object.assign(result, await hashDirectory(fullPath));
    } else if (entry.isFile()) {
      result[fullPath] = await hashFile(fullPath);
    }
  }

  return result;
}

export async function checkHashes(hashes) {
  const url = new URL(API_URL);
  url.searchParams.set("hashes", hashes.join(","));
  const response = await fetch(url, { headers: { Accept: "application/json" } });
  return await response.json();
}

const fileHash = await hashFile("example.bin");
console.log(fileHash);
console.log(await checkHashes([fileHash]));

PHP

<?php

const HASHCHECK_API_URL = 'https://hashcheck.xexle.com/api/check';

function hashFileSha256(string $path): string
{
    return hash_file('sha256', $path);
}

function hashDirectorySha256(string $path): array
{
    $out = [];
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)
    );

    foreach ($iterator as $file) {
        if ($file->isFile()) {
            $out[$file->getPathname()] = hashFileSha256($file->getPathname());
        }
    }

    return $out;
}

function checkHashes(array $hashes): array
{
    $query = http_build_query(['hashes' => implode(',', $hashes)]);
    $json = file_get_contents(HASHCHECK_API_URL . '?' . $query);
    return json_decode($json ?: '{}', true) ?: [];
}

$fileHash = hashFileSha256('example.bin');
print_r(checkHashes([$fileHash]));

Go

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const apiURL = "https://hashcheck.xexle.com/api/check"

func HashFile(path string) (string, error) {
	file, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer file.Close()

	sum := sha256.New()
	if _, err := io.Copy(sum, file); err != nil {
		return "", err
	}

	return hex.EncodeToString(sum.Sum(nil)), nil
}

func HashDirectory(root string) (map[string]string, error) {
	out := map[string]string{}
	err := filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
		if err != nil || entry.IsDir() {
			return err
		}
		hash, err := HashFile(path)
		if err != nil {
			return err
		}
		out[path] = hash
		return nil
	})
	return out, err
}

func CheckHashes(hashes []string) (map[string]any, error) {
	client := &http.Client{Timeout: 30 * time.Second}
	values := url.Values{}
	values.Set("hashes", strings.Join(hashes, ","))

	resp, err := client.Get(apiURL + "?" + values.Encode())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var result map[string]any
	err = json.NewDecoder(resp.Body).Decode(&result)
	return result, err
}

func main() {
	hash, err := HashFile("example.bin")
	if err != nil {
		panic(err)
	}
	result, err := CheckHashes([]string{hash})
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
}

Bash

#!/usr/bin/env bash
set -euo pipefail

API_URL="https://hashcheck.xexle.com/api/check"

hash_file() {
  shasum -a 256 "$1" | awk '{print $1}'
}

hash_directory() {
  find "$1" -type f -print0 | sort -z | while IFS= read -r -d '' file; do
    printf '%s  %s\n' "$(hash_file "$file")" "$file"
  done
}

check_hashes() {
  local hashes="$1"
  curl -sS -G "$API_URL" --data-urlencode "hashes=$hashes"
}

file_hash="$(hash_file "example.bin")"
check_hashes "$file_hash"

Integration Notes