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('myHookName')
    async handleWebhook(req: Request, res: Response) {
        const data = req.body;
        res.status(200).json({ status: "success", received: data });
    }
}

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

The function being decorated should take in an express Request and Response, which you can access directly to read and write data.


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

If you don’t need a hook to access any agent state, you can define it as a static hook.

@hook('myStaticHook')
static async doSomething(req: Request, res: Response) {
    res.status(200).json({ success: true });
}

Session Routing for Hooks

If you are unfamiliar with sessions, you should first review the concept of Sessions.

You can direct the hook to a specific session id using the session argument of the second parameter of the decorator. This argument is an optionally async function that takes in the request, and returns the session id.

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

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

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.