Webhooks
Beta: The Glossi API is currently in beta. Endpoints, request/response formats, and behavior may change as we iterate. If you run into issues or have feedback, reach out at support@glossi.io.
Instead of polling for status changes, you can receive notifications when important events occur. Glossi will send HTTP POST requests to your configured endpoint.
Available Events
| Event | Description |
|---|---|
model.processed | Model upload processing completed successfully |
model.failed | Model processing failed |
project.created | A project was created |
job.complete | A job workflow completed successfully |
job.failed | A job workflow failed |
render.complete | A render completed successfully |
render.failed | A render failed |
Configure Your Webhook
You can create or update your webhook configuration using the API.
Create/Update Webhook
Endpoint:
PUT https://api.glossi.app/api/v1/webhooksHeaders:
| Header | Value |
|---|---|
X-API-Key | Your API key |
Content-Type | application/json |
Body:
{
"url": "https://your-app.com/webhooks/glossi",
"events": [
"model.processed",
"model.failed",
"project.created",
"job.complete",
"job.failed",
"render.complete",
"render.failed"
],
"enabled": true,
"description": "Production webhook for n8n"
}Response:
{
"id": "webhook-uuid",
"url": "https://your-app.com/webhooks/glossi",
"secret": "your-webhook-secret",
"events": ["model.processed", "model.failed", "project.created", "job.complete", "job.failed", "render.complete", "render.failed"],
"enabled": true,
"description": "Production webhook for n8n"
}Important: Save the
secretvalue - you'll need it to verify webhook signatures. This is only shown once when creating or updating the webhook.
Get Current Webhook
Endpoint:
GET https://api.glossi.app/api/v1/webhooksHeaders:
| Header | Value |
|---|---|
X-API-Key | Your API key |
Response:
{
"id": "webhook-uuid",
"url": "https://your-app.com/webhooks/glossi",
"events": ["model.processed", "render.complete"],
"enabled": true,
"description": "Production webhook",
"lastTriggeredAt": "2025-01-15T10:30:00.000Z",
"lastResponseStatus": 200,
"failureCount": 0
}Delete Webhook
Endpoint:
DELETE https://api.glossi.app/api/v1/webhooksHeaders:
| Header | Value |
|---|---|
X-API-Key | Your API key |
Webhook Payloads
All webhooks follow this format:
{
"event": "event.name",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
// Event-specific data
}
}model.processed
Sent when a model finishes processing and is ready to use.
{
"event": "model.processed",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"modelId": "model-uuid",
"name": "Chair Model",
"status": "READY",
"glbFilePath": "https://s3.../file.glb",
"thumbnailUrl": "https://s3.../file.jpg"
}
}model.failed
Sent when model processing fails.
{
"event": "model.failed",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"modelId": "model-uuid",
"name": "Chair Model",
"status": "FAILED",
"error": "Model conversion failed: unsupported format"
}
}project.created
Sent when a project is created.
{
"event": "project.created",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"projectId": "project-uuid",
"name": "Chair Project",
"modelIds": ["model-uuid"],
"templateId": "template-uuid"
}
}job.complete
Sent when a Jobs API workflow completes successfully.
{
"event": "job.complete",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"jobId": "job-uuid",
"status": "COMPLETE",
"results": [
{
"model": { "id": "model-uuid", "name": "Chair" },
"project": { "id": "project-uuid", "name": "Chair Project" }
}
]
}
}job.failed
Sent when a Jobs API workflow fails.
{
"event": "job.failed",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"jobId": "job-uuid",
"status": "FAILED",
"error": "Model processing timed out"
}
}render.complete
Sent when a render job completes successfully.
{
"event": "render.complete",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"jobId": "render-job-uuid",
"status": "COMPLETED",
"projectId": "project-uuid",
"renderIds": ["render-uuid-1", "render-uuid-2"]
}
}render.failed
Sent when a render job fails.
{
"event": "render.failed",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"jobId": "render-job-uuid",
"projectId": "project-uuid",
"error": "Render timed out"
}
}Verify Webhook Signatures
All webhook requests include an X-Glossi-Signature header. Always verify this signature to ensure the request came from Glossi and hasn't been tampered with.
The signature is an HMAC-SHA256 hash of the request body using your webhook secret.
JavaScript/Node.js
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware example
app.post('/webhooks/glossi', (req, res) => {
const signature = req.headers['x-glossi-signature'];
const secret = process.env.GLOSSI_WEBHOOK_SECRET;
if (!verifyWebhook(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const { event, data } = req.body;
console.log(`Received ${event}:`, data);
res.status(200).send('OK');
});Python
import hmac
import hashlib
import json
def verify_webhook(payload: dict, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask example
@app.route('/webhooks/glossi', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Glossi-Signature')
secret = os.environ['GLOSSI_WEBHOOK_SECRET']
if not verify_webhook(request.json, signature, secret):
return 'Invalid signature', 401
event = request.json['event']
data = request.json['data']
print(f'Received {event}: {data}')
return 'OK', 200PHP
function verifyWebhook($payload, $signature, $secret) {
$expected = hash_hmac('sha256', json_encode($payload), $secret);
return hash_equals($expected, $signature);
}
// Usage
$signature = $_SERVER['HTTP_X_GLOSSI_SIGNATURE'];
$payload = json_decode(file_get_contents('php://input'), true);
$secret = getenv('GLOSSI_WEBHOOK_SECRET');
if (!verifyWebhook($payload, $signature, $secret)) {
http_response_code(401);
exit('Invalid signature');
}
// Process webhook
$event = $payload['event'];
$data = $payload['data'];Retry Behavior
If your webhook endpoint returns a non-2xx status code, Glossi will retry the delivery:
- Retry attempts: 3
- Retry delay: Exponential backoff (1 min, 5 min, 30 min)
- Timeout: 30 seconds per request
After all retries fail, the failureCount on your webhook configuration will increment. You can check this via GET /api/v1/webhooks.
Best Practices
- Always verify signatures - Never trust webhook data without verifying the signature
- Respond quickly - Return a 200 status within 5 seconds; process asynchronously if needed
- Handle duplicates - Webhooks may be delivered more than once; use the
jobIdor event data to deduplicate - Use HTTPS - Always use HTTPS endpoints in production
- Log everything - Keep logs of received webhooks for debugging
Testing Webhooks
For local development, use a tunneling service like ngrok to expose your local server:
# Start ngrok
ngrok http 3000
# Use the ngrok URL when configuring your webhook
# https://abc123.ngrok.io/webhooks/glossiYou can also use webhook testing services like webhook.site to inspect payloads without writing code.