Glossi Docs
Glossi API

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

EventDescription
model.processedModel upload processing completed successfully
model.failedModel processing failed
project.createdA project was created
job.completeA job workflow completed successfully
job.failedA job workflow failed
render.completeA render completed successfully
render.failedA 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/webhooks

Headers:

HeaderValue
X-API-KeyYour API key
Content-Typeapplication/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 secret value - 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/webhooks

Headers:

HeaderValue
X-API-KeyYour 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/webhooks

Headers:

HeaderValue
X-API-KeyYour 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', 200

PHP

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

  1. Always verify signatures - Never trust webhook data without verifying the signature
  2. Respond quickly - Return a 200 status within 5 seconds; process asynchronously if needed
  3. Handle duplicates - Webhooks may be delivered more than once; use the jobId or event data to deduplicate
  4. Use HTTPS - Always use HTTPS endpoints in production
  5. 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/glossi

You can also use webhook testing services like webhook.site to inspect payloads without writing code.

On this page