Skip to main content
POST
/
v1
/
tunnels
/
:id
/
heartbeat
Send Heartbeat
curl --request POST \
  --url https://api.example.com/v1/tunnels/:id/heartbeat
{
  "ok": true,
  "expiresAt": "<string>",
  "INVALID_TUNNEL_ID": {},
  "TUNNEL_NOT_FOUND": {}
}
Send a heartbeat to keep a tunnel alive and extend its lease.

Authentication

Requires a valid access token in the Authorization header:
Authorization: Bearer <access_token>

Path Parameters

id
string
required
Tunnel identifier. Can be either:
  • The tunnel UUID (e.g., a1b2c3d4-e5f6-7890-abcd-ef1234567890)
  • The tunnel hostname (e.g., my-app.example.com)

Request Body

No request body is required.

Response

ok
boolean
Always true on success.
expiresAt
string
ISO 8601 timestamp indicating when the tunnel lease will expire if no further heartbeats are received.

Error Responses

INVALID_TUNNEL_ID
400
The tunnel identifier is missing or invalid.
TUNNEL_NOT_FOUND
404
The tunnel was not found or is not active for this user.

Heartbeat Mechanism

Heartbeat Interval: Clients should send heartbeats every 20 seconds.Lease Timeout: Tunnels expire 60 seconds after the last heartbeat.Grace Period: This provides a 40-second grace period if a heartbeat is delayed.

Lifecycle

  1. When a tunnel is created, an initial lease is established
  2. The client must send heartbeat requests at 20-second intervals
  3. Each successful heartbeat extends the lease by 60 seconds from the current time
  4. If no heartbeat is received within 60 seconds, the tunnel is marked as expired
  5. Expired tunnels are automatically cleaned up by the system
If your client crashes or loses network connectivity, the tunnel will automatically expire after 60 seconds and be cleaned up, including DNS record deletion.

Example Request

curl -X POST https://api.example.com/v1/tunnels/a1b2c3d4-e5f6-7890-abcd-ef1234567890/heartbeat \
  -H "Authorization: Bearer <access_token>"

Example Response

{
  "ok": true,
  "expiresAt": "2026-03-05T10:33:15.000Z"
}

Implementation Example

// Send heartbeat every 20 seconds
const HEARTBEAT_INTERVAL = 20_000; // 20 seconds in milliseconds

const heartbeatInterval = setInterval(async () => {
  try {
    const response = await fetch(
      `https://api.example.com/v1/tunnels/${tunnelId}/heartbeat`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${accessToken}`,
        },
      }
    );
    
    if (!response.ok) {
      console.error('Heartbeat failed:', response.status);
      // Implement retry logic or cleanup
    }
    
    const data = await response.json();
    console.log('Lease expires at:', data.expiresAt);
  } catch (error) {
    console.error('Heartbeat error:', error);
    // Implement retry logic
  }
}, HEARTBEAT_INTERVAL);

// Clean up on shutdown
process.on('SIGINT', () => {
  clearInterval(heartbeatInterval);
  // Delete tunnel
});
Always implement proper error handling and cleanup in your heartbeat logic to ensure tunnels are properly deleted when your application shuts down.

Build docs developers (and LLMs) love