Webhooks let your integration receive real-time updates from Hyperspell. Whenever a memory finishes indexing, or a user connects a new source, Hyperspell will send a secure HTTP POST request to your webhook endpoint.
To receive webhooks, navigate to the Dashboard and enter a webhook URL. It is important that your webhook endpoint returns a 200 OK response to Hyperspell’s request.
Webhook payload
You will receive a JSON payload with the following fields:
The event that triggered the webhook. Can be index-started
or index-completed
.
The app slug that triggered the webhook.
The user id that triggered the webhook. Maybe be null
.
The source that triggered the webhook, ie. notion
, slack
, web_crawler
, etc.
The resource id that triggered the webhook. Maybe be null
. For the web_crawler
source, this is the URL that started the crawl.
The timestamp of the event in seconds since the Unix epoch.
Additionally, the following headers are included in the request:
A SHA256 hash of the webhook payload using the app secret as the key. You can verify the hash to ensure the request is from Hyperspell.
Verifying the webhook
To verify that the webhook is coming from Hyperspell, you can use the X-Hyperspell-Signature
header. This is a SHA256 hash of the webhook payload using the app’s secret as the key. You can get the secret from the Dashboard (it’s the same as the JWT Secret
)
You can verify the hash to ensure the request is from Hyperspell. Here’s an example of how to receive and verify the webhook:
from fastapi import FastAPI, Request
import hashlib
import hmac
def verify_webhook(payload, secret):
hash = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(signagture, hash)
class WebhookPayload(pydantic.BaseModel):
event: str
app: str
user_id: str | None
source: str
resource_id: str | None
timestamp: int
app = FastAPI()
@router.post("/webhooks/hyperspell")
async def hyperspell_webhook(query: WebhookPayload, request: Request):
# Verify that the request is from Hyperspell
signature = request.headers.get("X-Hyperspell-Signature")
if not verify_webhook(query, HYPERSPELL_JWT_SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
# Protect against replay attacks
if query.timestamp < time.time() - 300:
raise HTTPException(status_code=401, detail="Timestamp is too old")
# Handle the webhook
if query.event == "index-started":
# Handle the index started event
...
elif query.event == "index-completed":
# Handle the index completed event
...
return {"message": "Webhook received"}