The Developer’s Guide to the ShortlyLink API and Webhooks
A developer’s guide to integrating ShortlyLink: the /api/v1 REST API with x-api-key auth, the four link endpoints, HMAC-signed webhooks, the conversion postback, and the Slack command.
ShortlyLink gives developers a small, honest surface to build on: a public REST API for creating and tracking links, plus HMAC-signed webhooks, a signed conversion postback flow, custom domains, and a Slack slash command. The API is deliberately narrow — four endpoints, links only — so most of your real integration logic lives in webhooks and the conversion path, not in polling. This guide walks through authentication, every endpoint, and the richer event-driven surface, with copy-paste curl examples.
Quick answer
- The public REST API lives under
/api/v1and authenticates with anx-api-keyheader. It is not Bearer auth — sendingAuthorization: Bearer ...returns401. - API keys look like
sl_followed by 64 hex characters and are shown once at creation. Store it immediately. - There are four endpoints only: list links, create a link, get a link's stats, and delete a link. There is no update endpoint — editing a link means creating a new one.
- The heavier lifting happens outside REST: webhooks (click, conversion, bot_detected, link_expired, anomaly), the signed conversion postback (
cid/exp/sig, 24h TTL, deduped), custom domains via DNS TXT, and a signature-verified/shortlySlack command.
How do I authenticate to the ShortlyLink API?
Mint an API key from your account settings. The key is generated once, displayed once, and never shown again — if you lose it, you rotate it and get a new one. The format is stable and easy to validate: the literal prefix sl_ plus 64 hexadecimal characters.
Every request to /api/v1 carries the key in the x-api-key header. This is the single most common integration mistake, so it is worth stating plainly: ShortlyLink does not use Bearer tokens. A request with Authorization: Bearer sl_xxxxx is rejected with 401 Unauthorized. Use the header below instead.
curl https://shortly-link.onrender.com/api/v1/links \
-H "x-api-key: sl_xxxxx"
Treat the key like a password: keep it in a secret manager or environment variable, never commit it, and never ship it to a browser. The API is a server-to-server surface. For anything that runs in a user's browser (conversion pixels, bio pages), you use the dedicated public endpoints, not your API key.
What can the REST API actually do?
Four things. That is the entire contract, and knowing the boundary up front saves you from designing around endpoints that do not exist.
| Method | Path | Purpose |
|---|---|---|
GET |
/api/v1/links |
List your links (paginated, default 20 per page) |
POST |
/api/v1/links |
Create a short link |
GET |
/api/v1/links/:code/stats |
Get click/analytics stats for one link |
DELETE |
/api/v1/links/:code |
Delete a link |
Notice what is absent: there is no PATCH or PUT. You cannot update a destination, back-half, or targeting rule through the API. That is a deliberate design choice, and it shapes how you should build. More on that in the "no update endpoint" section below.
List your links
GET /api/v1/links returns your links, newest first, paginated with a default page size of 20. Use it to reconcile state, back up your link inventory, or drive a dashboard.
curl "https://shortly-link.onrender.com/api/v1/links?page=1" \
-H "x-api-key: sl_xxxxx"
Because pages are capped, treat listing as an iteration: request page 1, then page 2, and so on until a page comes back short. Do not assume all your links fit in a single response.
Create a link
POST /api/v1/links is where you do most of your writing. Send JSON with your destination URL and any options you want baked in at creation — a custom back-half, a campaign, tags. Set these correctly the first time, because you cannot change them later through the API.
curl -X POST https://shortly-link.onrender.com/api/v1/links \
-H "x-api-key: sl_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"originalUrl": "https://example.com/offer?ref=partner",
"customCode": "summer-deal",
"campaign": "q3-launch",
"tags": ["affiliate", "email"]
}'
Custom back-halves must be 3–50 characters, drawn from the same alphabet as auto codes (letters, digits, _, -), unique across the system, and clear of reserved words. Auto-generated codes are 8 characters if you omit customCode. Lookups are case-insensitive, so Summer-Deal and summer-deal resolve to the same link — plan your codes accordingly.
Get a link's stats
GET /api/v1/links/:code/stats returns the analytics rollup for a single link by its code. This is your pull-based path for reporting when you would rather query than subscribe to webhooks.
curl https://shortly-link.onrender.com/api/v1/links/summer-deal/stats \
-H "x-api-key: sl_xxxxx"
Remember that bot traffic is excluded from stats automatically, so the numbers you read here are already filtered. One practical gotcha worth internalizing: do not test your links with curl. A bare curl request looks exactly like a scripting bot to the fraud engine and trips the block tier, returning a 403. That is fine for hitting the API (which authenticates you), but hitting a short link itself with curl will get blocked and will not register as a real click. Test redirects in a real browser.
Delete a link
DELETE /api/v1/links/:code removes a link. It is the second half of the create/delete pair you will lean on constantly.
curl -X DELETE https://shortly-link.onrender.com/api/v1/links/summer-deal \
-H "x-api-key: sl_xxxxx"
Deletion is destructive: the code stops resolving and its analytics go with it. If you might need the history, list or export before you delete.
Why is there no update endpoint, and how do I work around it?
This is the single most important architectural point for anyone building on ShortlyLink. There is no way to edit a link through the API. You design around a create-plus-delete model instead.
The reason is integrity. A short code is bound to its destination and its analytics. If you could silently repoint a code, every historical click recorded against it would now describe a different destination — your data would lie to you. By making links effectively immutable through the API, ShortlyLink keeps each code's analytics honest.
The trade-off is real and you should account for it: editing means minting a new short code, and the old link's analytics become orphaned from the new one. So:
- Decide destinations before you publish. Get the
originalUrl, back-half, campaign, and tags right atPOSTtime. - When a destination genuinely changes, create a new link and update wherever the link is referenced (email, bio page, ad). Do not expect the old code to follow.
- Never bind a printed asset — a QR code, a flyer, a business card — to a destination you expect to change, unless you are prepared to reprint. An immutable code is a feature here, but only if you respect it.
- Keep your own mapping of "logical campaign" to "current short code" in your system, so a destination change is a new-code event you control rather than a surprise.
If you need destinations you can safely repoint over time, that is a job for targeting rules configured in the app (geo, rotation, A/B, deep links) rather than the API — see Features for the full redirect toolkit.
How do webhooks work?
Webhooks are where ShortlyLink stops being a request/response API and becomes event-driven. Instead of polling /stats on a timer, you register an endpoint and ShortlyLink pushes events to you as they happen. This is the recommended way to keep an external system in sync.
Five event types are emitted:
click— a (non-bot) click was recorded on one of your links.conversion— a conversion was registered against a link (see postbacks below).bot_detected— the fraud engine flagged and handled bot traffic.link_expired— a scheduledexpiresAtfired and a link auto-expired.anomaly— the anomaly job (which runs every 5 minutes) detected a traffic spike or bot surge.
Delivery is HMAC-signed, so you can verify that a payload really came from ShortlyLink and was not tampered with in transit. Compute the HMAC of the raw request body using your webhook secret and compare it (in constant time) against the signature header before you trust the payload. Reject anything that does not match.
Delivery is also SSRF-guarded: ShortlyLink validates outbound webhook targets so the delivery mechanism cannot be tricked into reaching internal or private network addresses. In practice this means your webhook URL should be a normal, publicly reachable HTTPS endpoint.
A sound webhook consumer follows a few rules:
- Verify the HMAC signature first. Compute over the exact raw bytes you received, before any JSON parsing or re-serialization.
- Respond fast, process async. Acknowledge with a
2xxquickly and hand the payload to a queue. Slow handlers invite retries and duplicates. - Be idempotent. Design your handler so that receiving the same event twice has no extra effect — key on the event's identifiers.
How does the conversion postback flow work?
Conversions are how you attribute revenue back to a click, and ShortlyLink secures the whole path with a signature. This is the core of any affiliate integration. For a conceptual primer, see What is a postback URL.
Here is the mechanism. When a short link redirects a visitor, ShortlyLink appends three values to the destination URL:
cid— a click id that uniquely identifies this click.exp— an expiry timestamp for the signature.sig— an HMAC-SHA256 signature bindingcidandexptogether.
Your landing page (or your affiliate network) captures those three values. When the conversion happens — a sale, a signup — you send them back to ShortlyLink's postback endpoint:
curl "https://shortly-link.onrender.com/postback?cid=CLICK_ID&exp=EXPIRY&sig=SIGNATURE&amount=49.99&status=confirmed"
ShortlyLink re-verifies the signature, checks that it has not expired, and records the conversion against the original click. A few rules that will save you debugging time:
- The signature TTL is 24 hours. A postback that arrives more than a day after the click is rejected as expired. If your network settles conversions on a delay longer than that, account for it — the signed window is not infinite.
- Conversions are deduplicated by
cid. Fire the same click id twice and the second call returns409. There is no double counting, so safe retries are fine — a409means "already recorded," not "failed." - Payouts are clamped. The
amountis capped at the link'smaxPayout(or a default max of 1000), and negative,NaN, orInfinityvalues collapse to0. SetmaxPayoutwhen you expect specific commission ceilings. - Statuses normalize to
confirmed,pending, orrejected(anything unknown is treated asconfirmed).
If you cannot run a server-to-server postback, there is a browser alternative — a conversion pixel at POST /api/conversions/pixel — that returns the same cid/exp/sig triplet through the same signed, deduped path. Prefer the server-side postback when you can; it is more reliable than a browser call.
How do I add a custom domain?
Branded short domains are configured in the app rather than the API. You add a domain, prove you own it by publishing a DNS TXT record that ShortlyLink gives you, and once it verifies you can assign that domain per link. This is a one-time DNS step per domain; after verification your links can serve from your own branded host.
What is the Slack /shortly command?
ShortlyLink integrates with Slack in two directions. Outbound, it can post conversion and anomaly alerts into a channel — useful for wiring the anomaly and conversion events to a place your team actually watches. Inbound, it registers a /shortly slash command so teammates can act on links without leaving Slack.
The slash command is signature-verified: ShortlyLink validates Slack's request signature on every invocation, so only genuine requests from your workspace are honored. That verification is what makes it safe to expose an actionable command over a public endpoint.
Putting it together
A typical ShortlyLink integration looks like this: use the REST API to create links (with maxPayout and campaign set up front) and to read stats or delete links when you clean up. Never plan to edit — create a fresh code instead. Subscribe to webhooks for real-time clicks, conversions, expirations, and anomalies, verifying every HMAC. Wire your affiliate network's conversion tracking through the signed postback, respecting the 24-hour TTL and the 409 dedup contract. Add a custom domain for branding and the /shortly command so your team can operate from Slack.
Four endpoints, five events, one signed conversion path. It is a small surface on purpose, and that is what makes it predictable to build on.
Ready to build? Start free at /register — no credit card, beta access.