Skip to content

Documentation navigation

Local webhook tunnels

Set up connections, serve agents and test signed callbacks before deployment.

#Test incoming webhooks locally

maivn dev-tunnel gives selected local connections temporary HTTPS callback URLs. It runs from a terminal with the SDK installed. Once your connection and project API key are configured, you can keep the agent and tunnel running without Studio or the Portal. First-time connection setup has a separate sign-in requirement. See Connection setup and terminal authentication.

The command uses an installed cloudflared client, licensed under Apache-2.0, and Cloudflare's free hosted Quick Tunnel service. No Cloudflare account, domain, paid plan, or additional Python dependency is required. The hosted service is operated by Cloudflare, and its availability is separate from the client's open-source license.

#Prepare your local project

  1. Start your local platform so its API answers at http://127.0.0.1:8000. The tunnel command forwards to this fixed local address.
  2. Use an SDK build whose maivn --help lists dev-tunnel. In a project managed by uv, run commands as uv run maivn ... in that project's environment. In a platform source checkout, use uv run --frozen maivn ... from libraries/maivn-sdk. Use an absolute path to an agent outside that package.
  3. Run maivn dev-tunnel --install. This explicit setup downloads the official cloudflared release pinned by your SDK, verifies its SHA-256 checksum, and stores it in ~/.maivn/tools/cloudflared/. It needs no administrator access, PATH changes, Python extra, or Cloudflare account. Installation never opens a tunnel. An existing compatible client on PATH is also supported. Use the official downloads for platforms without a matching automatic download.
  4. Create an incoming connection in the same local project as your agent. Keep its connection ID and configure the provider's signing secret through the connection setup. The tunnel does not create connections or secrets.
  5. Put that connection ID in your app's trigger declaration and register the trigger for the agent ID and version you will serve. If you use Studio, reload the app after editing the declaration and complete its trigger Setup steps. Start the agent in a terminal or use Run it from Studio when offered.

Check the tools before opening a tunnel:

Shell
maivn --help
maivn serve --help
maivn dev-tunnel --help

The help commands do not open a public tunnel.

#Set up and control the tunnel in Studio

Open Config → Triggers → Connections (or your trigger's connection setup), then find Test webhooks locally. Both the project and Studio must use the local platform's API at http://127.0.0.1:8000.

  1. If the client is missing, choose Set up local tunnel. Wait for installation to finish. This uses the same verified installer as the terminal command.
  2. Choose the incoming Connection to expose, then Start tunnel.
  3. Choose Copy callback URL and paste it into the provider's webhook settings. An allocated address can take a moment to become reachable through public DNS. Send a provider test and inspect the trigger's run to confirm delivery.
  4. Choose Stop tunnel when finished. Closing the setup panel keeps it running, but closing Studio stops it. Starting again gives a new callback URL, which must replace the old URL in the provider settings.

One Studio tunnel can run at a time. Its callback exposes only the selected signed receiver. The rest of the platform stays private. A failed installation can be retried, and an unverified download is discarded. Stop remains available after sign-out or an upstream outage. Quick Tunnels do not support SSE.

The Developer Portal creates the connection and helps configure your provider. Use Studio running on your machine or the terminal commands to install and run the local tunnel. A hosted Portal cannot install software on your computer.

#Connection setup and terminal authentication

In Studio, use the project's Connections setup or the connection step in an app's trigger setup. In the Developer Portal, select the project and open Connections, then Add connection. Create an incoming connection of the correct provider type and retain any one-time secret before closing its reveal. These screens must point to your local platform for this local workflow. A connection created in a hosted project does not automatically exist locally.

The connection API is also available to terminal HTTP clients, but it requires a signed-in human session. Project MAIVN_API_KEY bearer tokens and user JWT bearer tokens do not authenticate these routes. Creating, updating, or revoking connections requires a project owner or admin and a CSRF token. Project members with read access can list them.

#Create a connection with the CLI

Run maivn connections create --help to see the supported connection kinds. The command prompts privately for your account password and any provider secret, creates one connection, then signs out. It uses the same human permissions as Studio and the Portal. It does not register a trigger or start an agent.

For example, create a Local files connection from any terminal with the SDK:

Shell
maivn connections create --project-id YOUR_PROJECT_ID --email YOU@example.com --name "Invoices folder" --kind local_file_watch --secret-file ~/.maivn-private/invoices.json

Replace the project ID and email with your own. On Windows, native curl.exe must be on PATH, while macOS and Linux use curl. The command talks to MAIVN_BASE_URL, or to the local platform at http://127.0.0.1:8000 when that is unset; pass --base-url with a trusted HTTPS origin for a remote platform.

The secret destination must be a new file in an owner-private directory. The command creates and protects a missing parent directory, checks permissions before signing in, and refuses an existing file, shared directory, symlink or junction. Choose a private location outside your source repository and cloud sync folders. The JSON file contains the one-time signing secret and connection receipt. The terminal prints only the connection ID, callback URL and status.

Connection--kindAdditional input
Incoming webhookincoming_webhook--secret-file NEW_PATH
Your databasedatabase_webhook--secret-file NEW_PATH
Email inboxemail_inbox--secret-file NEW_PATH
Form submissionform_webhook--secret-file NEW_PATH
Local fileslocal_file_watch--secret-file NEW_PATH
Slack appslack_appPrivate signing-secret prompt
GitHubgithub_appPrivate webhook-secret prompt
Stripestripe_webhook--stripe-mode test or live; private webhook-secret prompt
Discord inbounddiscord_webhook--discord-application-id ID --discord-public-key HEX --discord-protocol interactions or webhook_events
Outgoing webhookoutgoing_webhookPrivate HTTPS endpoint prompt
Slack outputslack_webhookPrivate HTTPS endpoint prompt
Discord outputdiscord_outputPrivate HTTPS endpoint prompt

For GitHub, for example:

Shell
maivn connections create --project-id YOUR_PROJECT_ID --email YOU@example.com --name "Repository events" --kind github_app

The command will ask for the provider secret privately. Keep passwords, signing secrets and private endpoint URLs out of command arguments. Automation can opt into --credentials-stdin, supplying one bounded JSON line directly from its secret store: password and the kind's required signing_secret, webhook_secret or endpoint_url. Do not echo that JSON into shell history or logs.

HTTPS session cookies stay in memory. Local HTTP uses curl's normal cookie engine and a temporary owner-private cookie folder, removed after the logout attempt. If you forcibly kill the process, it can leave a maivn-connection-session-* folder in your system temporary directory. Remove that folder after the command has stopped. A logout warning means server logout was not confirmed.

If creation was attempted but its result is unknown, check your project's Connections list before retrying. If the result says created_private_save_unconfirmed, preserve the private file for recovery and inspect the reported connection. The command never repeats the create request. If the one-time secret cannot be recovered, revoke that connection through the Portal or existing API and create a replacement.

#Serve your agent

For terminal serving, obtain your project API key through the normal project key setup, keep it in MAIVN_API_KEY, and select your local address. The SDK has one address knob (MAIVN_BASE_URL); point it at whatever single address your local platform answers requests on. In PowerShell:

Powershell
$env:MAIVN_BASE_URL = 'http://127.0.0.1:8000'
maivn serve myapp.agents:triage --project-id YOUR_PROJECT_ID --agent-id YOUR_AGENT_ID

Use your actual import target and IDs. myapp.agents:triage means the triage Agent object exported by the myapp.agents module, and app.py:triage works for a file. Add --version N when the registered trigger targets a version other than the default of 1. Wait for the CLI's Announced message and keep that terminal running. The API key belongs to the agent process; the tunnel needs no mAIvn API key. Use the default worker transport for this workflow.

#Open the receiver tunnel

In a second terminal, run:

Shell
maivn dev-tunnel --connection-id conn-your-webhook

Repeat the option to expose another connection in the same temporary tunnel:

Shell
maivn dev-tunnel --connection-id conn-your-webhook --connection-id conn-your-github

Use the actual connection ID from setup, not the connection's display name or the project ID. The command prints complete callback URLs, for example:

Plain text
https://random-words.trycloudflare.com/v1/connections/conn-your-webhook/events

Copy the complete URL for the matching connection into the provider's webhook configuration. Keep the path unchanged: the receiver uses it when verifying generic webhook signatures. GitHub and Slack signatures also reach the receiver unchanged, together with the original request bytes and delivery headers.

Send the provider's signed test delivery. An ordinary accepted event returns 202 with {"accepted":true}. Slack's URL-verification challenge has its own response. Then check the matching trigger's fire history and resulting run in Studio or the platform API. Admission does not mean a matching trigger has completed, and replaying the same delivery does not create another run. An HTTPS URL by itself does not prove connection setup, signature verification, trigger matching, or successful agent execution.

#What the tunnel exposes

Only POST /v1/connections/ID/events for the IDs explicitly passed on the command line reaches the fixed local API address. The proxy binds to loopback on an ephemeral port. Management routes, Studio, the Portal, other methods, unknown connection IDs, query strings, encoded paths, and alternate upstream URLs are refused. The platform still verifies provider signatures and deduplicates deliveries. The tunnel neither grants authorization nor weakens receiver checks.

The proxy limits request and response bodies to 1 MiB. The platform's receiver has a smaller 256 KiB request limit, so keep event bodies within that limit. Requests require one valid Content-Length; transfer-encoded requests are refused. Concurrent proxy requests are bounded, and upstream redirects are never followed or forwarded. Provider request bodies and headers are not logged. Cloudflared gets a temporary empty config and a minimal environment, so an existing named-tunnel configuration, tunnel token, or mAIvn API key is not inherited.

#Stop and troubleshoot

Press Ctrl-C in the tunnel terminal. The command stops its cloudflared child, closes its local receiver listener, and removes its temporary configuration. It does not stop your agent or local platform. Remove the temporary callback URL from the provider when you finish. Starting another Quick Tunnel produces a new URL.

  • Unknown command dev-tunnel: check maivn --help in your project's SDK environment. A different globally installed maivn may be on PATH.
  • cloudflared is missing: run maivn dev-tunnel --install, or choose Set up local tunnel in Studio. The managed client does not need to be on PATH.
  • The address is allocated but your browser cannot find it: allow time for public DNS propagation, then check the hostname with your network's resolver. A local resolver can return NXDOMAIN even when a public resolver has the new address. Check network filtering or ask your network operator if it persists. mAIvn does not change your machine's DNS settings. A running client alone does not prove a webhook was delivered. Confirm a provider test in the trigger's run.
  • Startup exits or times out: check the installed client version and network access to Cloudflare. The command removes its child and proxy on failure.
  • 404: verify the complete callback URL, connection ID, request method, and signature setup. The receiver deliberately does not disclose whether a refused connection exists.
  • 400: send a fixed-length request with one Content-Length header and no Transfer-Encoding header.
  • 413: the delivery exceeds the proxy's 1 MiB limit. Bodies above the receiver's smaller 256 KiB limit may instead receive its generic 404 refusal.
  • 502: check that the local platform's API answers at http://127.0.0.1:8000. Redirects and oversized upstream responses are also refused.
  • The delivery arrives but no run completes: check registration and the serving process separately, including project, connection, agent ID, version, event match, and whether the trigger is enabled. Keep MAIVN_BASE_URL pointed at the same local address the trigger was registered against, so the agent claims local fires.
  • Connection API returns 401 or 403: the serving API key does not sign you into connection setup. Check the human session, current CSRF token, and project role using the setup path above.

Quick Tunnels are temporary development endpoints with no uptime guarantee. Cloudflare limits them to 200 in-flight requests and does not support Server-Sent Events on them. Keep the agent's worker transport local. This command is for incoming receiver callbacks and does not publish the agent's streaming endpoint.