Skip to content
GitHub
Get started →

Webhook adapter (any language)

The webhook adapter is the escape hatch. If your data lives somewhere Spelo doesn’t ship an adapter for — an Elastic / Algolia / Meilisearch index, a GraphQL gateway, an ERP, a proprietary DB, a combination of sources — you write one endpoint in any language and Spelo calls it.

~30 minutes to a working integration.

The contract

Spelo → your endpoint, POST <endpoint>:

{
"collection": "properties",
"query": "pool",
"filters": [
{ "field": "beds", "operator": "eq", "value": 2 },
{ "field": "city", "operator": "eq", "value": "West Hollywood" },
{ "field": "amenities", "operator": "contains", "value": "pool" }
],
"sort_by": "price",
"sort_direction": "asc",
"limit": 5
}

Headers sent with every request:

  • Content-Type: application/json
  • User-Agent: Spelo-Widget/0.1 (WebhookAdapter)
  • X-Spelo-Signature: sha256=<hmac> — only if you configured a shared secret
  • X-Spelo-Timestamp: <ms> — only if you configured a shared secret

Operators you’ll receive: eq, neq, gt, gte, lt, lte, contains, in.

Your endpoint → Spelo, 200 OK:

{
"success": true,
"total": 42,
"returned": 5,
"items": [
{
"id": "p1",
"address": "123 Main St",
"beds": 2,
"price": 3200,
"amenities": ["pool", "gym"]
}
]
}

Items can have any shape — the AI reads fields dynamically. Keep keys simple (strings, numbers, booleans, arrays).

On error, return { "success": false, "total": 0, "returned": 0, "items": [], "error": "..." }. The AI will see the message and tell the user something went wrong.

Config shape

{
"type": "webhook",
"config": {
"endpoint": "https://yourcompany.com/voice-search",
"secret": "a-shared-hmac-secret",
"headers": {
"X-Tenant-ID": "acme"
},
"timeoutMs": 10000
},
"collections": {
"properties": { "source": "properties" }
}
}
  • endpoint — your HTTPS URL. Must start with https:// (or http://localhost in dev).
  • secret — optional. If set, every request gets a signed header (see below).
  • headers — optional. Passed through on every call.
  • timeoutMs — optional. Default 10,000 (10s).

Signature verification

If you set a secret, Spelo adds:

  • X-Spelo-Signature: sha256=<hex> — HMAC-SHA256 of the raw request body
  • X-Spelo-Timestamp: <ms> — request time in ms since epoch

Verify both to prevent replay and spoofing. We strongly recommend rejecting timestamps more than 5 minutes old.

Examples

import express from 'express'
import crypto from 'crypto'
const app = express()
app.use(express.json())
const SECRET = process.env.SPELO_SECRET
app.post('/voice-search', (req, res) => {
const sig = req.header('X-Spelo-Signature') || ''
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET)
.update(JSON.stringify(req.body)).digest('hex')
if (sig !== expected) return res.status(401).json({ error: 'Bad signature' })
const { collection, filters = [], query, limit = 5 } = req.body
const items = yourSearchFunction(collection, filters, query, limit)
res.json({
success: true,
total: items.length,
returned: Math.min(items.length, limit),
items: items.slice(0, limit),
})
})
app.listen(3000)

What your search function should do

For the request above, a real-estate backend might:

  1. Filter listings where beds = 2 AND city = 'West Hollywood' AND 'pool' IN amenities
  2. Substring-match description or address against "pool"
  3. Sort by price ASC
  4. Return the top 5 as a list of simple objects

If your backend is Elastic / Algolia / Meilisearch, translate the filters into their DSL. If it’s a custom DB, run a prepared SQL query. The only contract is the request/response shape.

Security checklist

FAQ

Can my endpoint call multiple backends? Yes. Your handler is free to query any number of backends internally and merge results.

Can I return streaming results? Not yet. Return a single JSON payload. The AI speaks after the full result is back.

Do I have to support every operator? No. Implement what your backend supports. Return an error for unsupported ops — the AI will relay it.

How do I test locally? Run your server at http://localhost:3000/voice-search, set endpoint: "http://localhost:3000/voice-search" in the config. Localhost is always allowed.

Can I have multiple webhook adapters for one site? Not directly — one adapter per site. But your webhook can fan out to multiple backends.

Troubleshooting

  • 500 Internal Server Error from your endpoint → Spelo returns success: false to the AI, which tells the user there was a problem. Check your server logs.
  • Timeout — default is 10s. Bump timeoutMs if your backend is slow, but voice users won’t wait that long. Optimize.
  • Signature mismatch — double-check you’re hashing the raw body bytes, not a parsed/re-serialized representation (JSON ordering matters).

More: Build a custom adapter for a native TypeScript adapter, if you outgrow the webhook.