Overview

Hooks in Magma allow you to expose HTTP endpoints that external services can call to interact with your agent. They’re perfect for handling webhooks, callbacks, and API integrations.

Implementation

To add a hook to your agent, use the @hook decorator:

import { MagmaAgent } from "@pompeii-labs/magma";
import { hook } from "@pompeii-labs/magma/decorators";
import { Request, Response } from 'express';

class MyAgent extends MagmaAgent {
    @hook('webhook')
    async handleWebhook(req: Request, res: Response) {
        const data = req.body;
        res.status(200).json({ status: "success", received: data });
    }
}

The first parameter is the name of the hook. The second parameter is optional, and is covered in the Advanced Usage section.


Hook URLs

When your agent is deployed, hooks are automatically exposed at:

https://api.magmadeploy.com/v1/agents/<AGENT_ID>/hooks/<HOOK_NAME>

Request Handling

Hooks support both GET and POST methods by default. The request and response objects are the standard Express Request and Response type, giving you access to:

  • req.query - URL query parameters
  • req.body - POST request body
  • req.headers - HTTP headers
  • req.params - URL parameters

You can also use the res object to send a response to the caller.

  • res.status(code).json(body) - Send a JSON response
  • res.send(body) - Send a string or buffer response
  • res.end() - End the response

For more information on the Express Request and Response objects, see the Express documentation.

Common Use Cases

OAuth Callbacks

class MyAgent extends MagmaAgent {
    @hook('oauth-callback')
    async handleOAuth(req: Request, res: Response) {
        const { code } = req.query;
        const tokens = await this.exchangeCodeForTokens(code as string);
        res.status(200).json({ success: true, tokens });
    }
}

Webhook Listeners

class MyAgent extends MagmaAgent {
    @hook('stripe-webhook')
    async handleStripeEvent(req: Request, res: Response) {
        const event = req.body;
        switch (event.type) {
            case 'payment.succeeded':
                await this.processPayment(event.data);
                break;
            case 'subscription.created':
                await this.activateSubscription(event.data);
                break;
        }

        res.status(200).json({ received: true });
    }
}

API Endpoints

class MyAgent extends MagmaAgent {
    @hook('status')
    async getStatus(req: Request, res: Response) {
        const metrics = await this.getSystemMetrics();
        res.status(200).json({
            status: "healthy",
            uptime: process.uptime(),
            metrics
        });
    }
}

Best Practices

  1. Validation: Always validate incoming data
@hook('user-update')
async updateUser(req: Request) {
    const { userId, data } = req.body;
    if (!userId || !data) {
        throw new Error('Missing required fields');
    }
    await this.updateUserData(userId, data);
}
  1. Error Handling: Implement proper error handling
@hook('process-data')
async processData(req: Request, res: Response) {
    try {
        const result = await this.processPayload(req.body);
        res.status(200).json({ success: true, result });
    } catch (error) {
        res.status(500).json({
            success: false,
            error: error.message
        });
    }
}
  1. Security: Implement authentication when needed
@hook('secure-endpoint')
async secureEndpoint(req: Request, res: Response) {
    const apiKey = req.headers['x-api-key'];
    if (!apiKey || !this.validateApiKey(apiKey)) {
        res.status(401).json({ error: 'Unauthorized' });
    }
    const data = await this.getSecureData();
    res.status(200).json({ success: true, data });
}

Hooks are publicly accessible endpoints. Always implement appropriate security measures for sensitive operations.


Advanced Usage

Static Hooks

Some hooks don’t need to access the agent’s context. You can declare them as static methods:

@hook('user-update')
static async updateUser(req: Request, res: Response) {
    const { userId, data } = req.body;
}

This hook will be handled without instantiating the agent.

Session-based Hooks

Sometimes you need a hook to be handled by a specific session. You can do this by utilizing the second parameter of the hook decorator:

@hook('user-update', HookRequest.Body('userId'))
async updateUser(req: Request, res: Response) {
    const { userId, data } = req.body;
}

This will direct the hook to the session with the id that matches the userId in the request body. To learn more about sessions, see the Sessions page.

Limitations

  • File uploads are limited to 10MB
  • Webhook timeout is 30 seconds
  • Rate limiting applies based on your plan

For long-running operations, consider using a job instead of a hook, or implement an async pattern with a status endpoint.