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:
event
string
required
The event that triggered the webhook. Can be index-started or index-completed.
app
string
required
The app slug that triggered the webhook.
user_id
string
The user id that triggered the webhook. Maybe be null.
source
string
required
The source that triggered the webhook, ie. notion, slack, web_crawler, etc.
resource_id
string
The resource id that triggered the webhook. Maybe be null. For the web_crawler source, this is the URL that started the crawl.
timestamp
int
The timestamp of the event in seconds since the Unix epoch.
X-Hyperspell-Signature
string
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"}