Configure Portal webhooks
Webhooks let your application react to events in real time. Instead of polling the API for new packages, you register a URL and MASV sends an HTTP request whenever a specified event occurs. In this tutorial, you configure a webhook that fires when a package is uploaded to your portal.
What you’ll learn
Section titled “What you’ll learn”- Create a webhook subscribed to portal package events
- Attach the webhook to a portal
- Parse the webhook payload and extract package information
- Secure your webhook endpoint
Prerequisites
Section titled “Prerequisites”- A MASV API key. Store it in the
API_KEYenvironment variable. - A portal configured in your Team.
- An HTTPS endpoint that can receive POST requests (use webhook.site for testing).
- Your Team ID and portal ID stored in
TEAM_IDandPORTAL_IDenvironment variables.
Step 1: Create a webhook
Section titled “Step 1: Create a webhook”Register a webhook endpoint and subscribe it to package.finalized events:
# Store your API key in an environment variable — never hardcode it.# See /api/api-keys/ for key management best practices.curl -X POST "https://api.massive.app/v1/teams/$TEAM_ID/custom_webhooks" \ -H "X-API-KEY: $API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"active\": true, \"method\": \"POST\", \"name\": \"Portal Upload Notifications\", \"url\": \"https://your-server.example.com/webhooks/masv\", \"events\": [\"package.finalized\"], \"headers\": {\"X-Webhook-Secret\": \"$WEBHOOK_SECRET\"} }"The headers field lets you include a shared secret that your endpoint can validate on every incoming request. This is the primary mechanism for verifying that requests originate from MASV.
A successful response returns 201 Created with the webhook object:
{ "id": "<WEBHOOK_ID>", "active": true, "method": "POST", "name": "Portal Upload Notifications", "url": "https://your-server.example.com/webhooks/masv", "events": ["package.finalized"], "headers": {"X-Webhook-Secret": "<YOUR_SHARED_SECRET>"}}Step 2: Attach the webhook to a portal
Section titled “Step 2: Attach the webhook to a portal”Webhooks must be attached to a portal to receive events from it. Update your portal to include the webhook:
curl -X PUT "https://api.massive.app/v1/portals/$PORTAL_ID" \ -H "X-API-KEY: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "...", "subdomain": "...", "active": true, "custom_webhooks": [{"id": "$WEBHOOK_ID"}] }'Step 3: Test the webhook
Section titled “Step 3: Test the webhook”Upload a file to your portal to trigger the webhook. Use the MASV web interface, the Agent CLI, or the API.
After the upload finalizes, MASV sends a POST request to your registered URL. The payload looks like this:
{ "event_id": "<EVENT_ID>", "event_time": "2026-05-04T12:05:30.000Z", "event_type": "package.finalized", "body_extras": {}, "object": { "type": "package", "id": "<PACKAGE_ID>", "name": "project-assets", "portal_id": "<PORTAL_ID>", "sender": "sender@example.com", "size": 524288000, "state": "finalized", "total_files": 3, "created_at": "2026-05-04T12:00:00.000Z", "updated_at": "2026-05-04T12:05:30.000Z" }, "custom_webhook_id": "<WEBHOOK_ID>"}Confirm your endpoint received the request and returned a 200 status code. MASV retries failed deliveries up to 5 times with incremental backoff.
Step 4: Secure your endpoint
Section titled “Step 4: Secure your endpoint”MASV does not include a cryptographic signature on webhook payloads. Use the custom headers field (set in Step 1) to verify requests:
import osimport hmacfrom flask import Flask, request, abort
app = Flask(__name__)WEBHOOK_SECRET = os.environ["MASV_WEBHOOK_SECRET"]
@app.route("/webhooks/masv", methods=["POST"])def handle_webhook(): # Verify the shared secret header received_secret = request.headers.get("X-Webhook-Secret", "") if not hmac.compare_digest(received_secret, WEBHOOK_SECRET): abort(401)
event = request.get_json() if event["event_type"] == "package.finalized": package_id = event["object"]["id"] sender = event["object"]["sender"] size = event["object"]["size"] print(f"New package {package_id} from {sender} ({size} bytes)") # Trigger your downstream workflow here
return "", 200Return a 200 response promptly. If your processing takes time, acknowledge the webhook immediately and handle the work asynchronously.
Next steps
Section titled “Next steps”- Webhooks reference — Full list of supported events and configuration options.
- Cloud Connections — Automatically route uploaded files to external storage.
- Automations — Set up Agent-based automated download workflows.