URLY.UZ

Integrations

Connect URLY.UZ to Zapier, Make, n8n and your own systems through the REST API, signed webhooks, MCP, the Telegram bot and the browser extension.

Download the OpenAPI specification API

Authentication

Every request carries an API key (dashboard → API keys). Give the key only the scopes it needs, for example webhooks:write for webhooks.

Authorization: Bearer urly_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Webhooks

Add a webhook endpoint in the dashboard or with POST /api/v1/webhooks. URLY sends every event as a JSON POST; the urly-webhook-id header stays the same across retries, so use it to drop duplicates.

Events
EventPurpose
link.createdlink created
link.updatedlink updated
link.deletedlink moved to trash
clicks.hourlyhourly click summary
link.clickedevery click (Business)
domain.verifieddomain verified
subscription.changedplan or subscription changed
curl -s https://urly.uz/api/v1/webhooks \
  -H "Authorization: Bearer $URLY_API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.uz/urly","events":["link.created","clicks.hourly"]}'

Verifying the signature

The urly-signature header looks like t=<time>,v1=<signature>. The signature is HMAC-SHA256("<time>.<raw body>") keyed with the endpoint secret. For 24 hours after a secret rotation two v1 values are sent. Reject requests older than 5 minutes.

// Node.js
const crypto = require("crypto");
function verify(rawBody, header, secret) {
  const items = header.split(",").map((kv) => kv.split("="));
  const t = Number(items.find(([k]) => k === "t")[1]);
  const want = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - t) < 300;
  return fresh && items.some(([k, v]) => k === "v1" && v.length === want.length &&
    crypto.timingSafeEqual(Buffer.from(v), Buffer.from(want)));
}
# Python
import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    items = [kv.split("=", 1) for kv in header.split(",")]
    t = int(next(v for k, v in items if k == "t"))
    want = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return abs(time.time() - t) < 300 and any(
        k == "v1" and hmac.compare_digest(v, want) for k, v in items)

On a non-2xx answer URLY retries up to 9 times, from 30 seconds to 12 hours apart. After repeated failures or a 410 answer the endpoint is turned off.

Zapier

In Zapier, connect URLY with Webhooks by Zapier and the API Request action:

  • Trigger: add the "Catch Hook" URL as a URLY webhook (REST hook: subscribe with POST /api/v1/webhooks, unsubscribe with DELETE /api/v1/webhooks/{id}).
  • Action: POST /api/v1/links creates a short link; the response has a short_url field.
  • Polling trigger: GET /api/v1/links?limit=25 returns the newest links first (deduplicate by id).

Make

In Make:

  1. Create a "Webhooks → Custom webhook" module and copy its address.
  2. Add the address under Webhooks in the URLY dashboard and pick the events.
  3. For actions, call the URLY API with an "HTTP → Make a request" module and an Authorization: Bearer header.

n8n

In n8n:

  1. Add a "Webhook" trigger node (POST) and register its production URL as a URLY webhook.
  2. Verify the signature in a "Code" node with the Node.js example above (turn on "Raw body" in the Webhook node).
  3. For actions, use an "HTTP Request" node with Header Auth (Authorization: Bearer …).

MCP

AI assistants (Claude, IDEs and other MCP clients) connect to /api/v1/mcp over Streamable HTTP with an API key.

{
  "mcpServers": {
    "urly": {
      "type": "http",
      "url": "https://urly.uz/api/v1/mcp",
      "headers": { "Authorization": "Bearer ${URLY_API_KEY}" }
    }
  }
}

Tools: create_link, list_links, get_link_stats, create_qr. They follow the key's scopes.

Telegram

Connect the bot in Settings → Telegram: shorten links in the chat and get statistics and QR codes.

Browser extension

The Chrome and Firefox extension shortens the current page in one click, copies the result and shows a QR code. It only uses the API key you enter and loads no remote code.