# Bulk create links Source: https://dub.co/docs/api-reference/endpoint/bulk-create-links post /links/bulk Bulk create up to 100 links for the authenticated workspace. Bulk link creation does not support [custom link previews](https://dub.co/help/article/custom-link-previews). Also, [webhook events](/concepts/webhooks/introduction) will not be triggered when using this endpoint. # Bulk delete links Source: https://dub.co/docs/api-reference/endpoint/bulk-delete-links delete /links/bulk Bulk delete up to 100 links for the authenticated workspace. This is a destructive action and cannot be undone. Proceed with caution. Also, [webhook events](/concepts/webhooks/introduction) will not be triggered when using this endpoint. # Bulk update links Source: https://dub.co/docs/api-reference/endpoint/bulk-update-links patch /links/bulk Bulk update up to 100 links with the same data for the authenticated workspace. This endpoint lets you update up to 100 links **with the same data**. Some potential use cases: * Tagging multiple links at once * Setting the same expiration date for multiple links * Updating UTM parameters for multiple links You cannot update the domain or key of a link with this endpoint. Also, [webhook events](/concepts/webhooks/introduction) will not be triggered when using this endpoint. # Check the availability of one or more domains Source: https://dub.co/docs/api-reference/endpoint/check-a-domain-availability get /domains/status Check if a domain name is available for purchase. You can check multiple domains at once. Checking a domain availability requires an [Enterprise plan](https://dub.co/enterprise) # Create a domain Source: https://dub.co/docs/api-reference/endpoint/create-a-domain post /domains Create a domain for the authenticated workspace. # Create a folder Source: https://dub.co/docs/api-reference/endpoint/create-a-folder post /folders Create a folder for the authenticated workspace. # Create a link Source: https://dub.co/docs/api-reference/endpoint/create-a-link post /links Create a link for the authenticated workspace. # Create a partner Source: https://dub.co/docs/api-reference/endpoint/create-a-partner post /partners Create a partner for a program. If partner exists, automatically enrolls them. Partners endpoints require an [Advanced plan](https://dub.co/pricing) subscription or higher. # Create a link for a partner Source: https://dub.co/docs/api-reference/endpoint/create-a-partner-link post /partners/links Create a link for a partner that is enrolled in your program. Partners endpoints require an [Advanced plan](https://dub.co/pricing) subscription or higher. # Create a tag Source: https://dub.co/docs/api-reference/endpoint/create-a-tag post /tags Create a tag for the authenticated workspace. # Delete a customer Source: https://dub.co/docs/api-reference/endpoint/delete-a-customer delete /customers/{id} Delete a customer from a workspace. # Delete a domain Source: https://dub.co/docs/api-reference/endpoint/delete-a-domain delete /domains/{slug} Delete a domain from a workspace. It cannot be undone. This will also delete all the links associated with the domain. # Delete a folder Source: https://dub.co/docs/api-reference/endpoint/delete-a-folder delete /folders/{id} Delete a folder from the workspace. All existing links will still work, but they will no longer be associated with this folder. # Delete a link Source: https://dub.co/docs/api-reference/endpoint/delete-a-link delete /links/{linkId} Delete a link for the authenticated workspace. # Register a domain Source: https://dub.co/docs/api-reference/endpoint/register-a-domain post /domains/register Register a domain for the authenticated workspace. Only available for Enterprise Plans. Domain registration is only available for certain [Enterprise customers](https://dub.co/enterprise). Please [contact us](https://dub.co/contact/sales) to get access. # Retrieve a customer Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-customer get /customers/{id} Retrieve a customer by ID for the authenticated workspace. # Retrieve a link Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-link get /links/info Retrieve the info for a link. You can retrieve a link by providing one of the following as a query parameter: * `domain` and `key`. * `linkId`. * `externalId`. # List all commissions Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-commissions get /commissions Retrieve a list of commissions for a program. Commissions endpoints require an [Business plan](https://dub.co/pricing) subscription or higher. # List all customers Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-customers get /customers Retrieve a list of customers for the authenticated workspace. # Retrieve a list of events Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-events get /events Retrieve a paginated list of events for the authenticated workspace. Events endpoints require a [Business plan](https://dub.co/pricing) subscription or higher. # Retrieve a list of folders Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-folders get /folders Retrieve a list of folders for the authenticated workspace. # Retrieve a list of links Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-links get /links Retrieve a paginated list of links for the authenticated workspace. # Retrieve a list of tags Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-tags get /tags Retrieve a list of tags for the authenticated workspace. # Retrieve a partner's links. Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-partners-links get /partners/links Retrieve a partner's links by their partner ID or tenant ID. Partners endpoints require an [Advanced plan](https://dub.co/pricing) subscription or higher. # Retrieve analytics Source: https://dub.co/docs/api-reference/endpoint/retrieve-analytics get /analytics Retrieve analytics for a link, a domain, or the authenticated workspace. The response type depends on the `event` and `type` query parameters. Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription or higher. # Retrieve links count Source: https://dub.co/docs/api-reference/endpoint/retrieve-number-of-links get /links/count Retrieve the number of links for the authenticated workspace. The provided query parameters allow filtering the returned links. # Retrieve analytics for a partner Source: https://dub.co/docs/api-reference/endpoint/retrieve-partner-analytics get /partners/analytics Retrieve analytics for a partner within a program. The response type vary based on the `groupBy` query parameter. Partners endpoints require an [Advanced plan](https://dub.co/pricing) subscription or higher. # Track a lead Source: https://dub.co/docs/api-reference/endpoint/track-lead post /track/lead Track a lead for a short link. Conversions endpoints require a [Business plan](https://dub.co/pricing) subscription or higher. # Track a sale Source: https://dub.co/docs/api-reference/endpoint/track-sale post /track/sale Track a sale for a short link. Conversions endpoints require a [Business plan](https://dub.co/pricing) subscription or higher. # Update a commission. Source: https://dub.co/docs/api-reference/endpoint/update-a-commission patch /commissions/{id} Update an existing commission amount. This is useful for handling refunds (partial or full) or fraudulent sales. Commissions endpoints require an [Business plan](https://dub.co/pricing) subscription or higher. # Update a customer Source: https://dub.co/docs/api-reference/endpoint/update-a-customer patch /customers/{id} Update a customer for the authenticated workspace. # Update a domain Source: https://dub.co/docs/api-reference/endpoint/update-a-domain patch /domains/{slug} Update a domain for the authenticated workspace. # Update a folder Source: https://dub.co/docs/api-reference/endpoint/update-a-folder patch /folders/{id} Update a folder in the workspace. # Update a link Source: https://dub.co/docs/api-reference/endpoint/update-a-link patch /links/{linkId} Update a link for the authenticated workspace. If there's no change, returns it as it is. # Update a tag Source: https://dub.co/docs/api-reference/endpoint/update-a-tag patch /tags/{id} Update a tag in the workspace. # Upsert a link Source: https://dub.co/docs/api-reference/endpoint/upsert-a-link put /links/upsert Upsert a link for the authenticated workspace by its URL. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created. # Upsert a link for a partner Source: https://dub.co/docs/api-reference/endpoint/upsert-a-partner-link put /partners/links/upsert Upsert a link for a partner that is enrolled in your program. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created. Partners endpoints require an [Advanced plan](https://dub.co/pricing) subscription or higher. # Introduction Source: https://dub.co/docs/api-reference/introduction Fundamental concepts of Dub's API. ## Base URL Dub's API is built on REST principles and is served over HTTPS. To ensure data privacy, unencrypted HTTP is not supported. The Base URL for all API endpoints is: ```bash Terminal https://api.dub.co ``` ## Authentication Authentication to Dub's API is performed via the Authorization header with a Bearer token. To authenticate, you need to include the Authorization header with the word `Bearer` followed by your API key in your requests like so: ```bash Terminal Authorization: Bearer dub_xxxxxx ``` Here are examples of how to authenticate with Dub's API in different programming languages: ```bash cURL curl --request GET \ --url https://api.dub.co/links \ --header 'Authorization: Bearer dub_xxxxxx' ``` ```javascript Node.js import { Dub } from "dub"; const dub = new Dub({ token: "dub_xxxxxx", }); // Make API calls const links = await dub.links.list(); ``` ```python Python from dub import Dub client = Dub(api_key="dub_xxxxxx") # Make API calls links = client.links.list() ``` ```go Go import ( "context" "github.com/dubinc/dub-go" ) client := dub.NewClient("dub_xxxxxx") // Make API calls ctx := context.Background() links, err := client.Links.List(ctx) ``` ```ruby Ruby require 'dub' client = Dub::Client.new(api_key: "dub_xxxxxx") # Make API calls links = client.links.list ``` ```php PHP use Dub\Client; $client = new Client([ 'api_key' => 'dub_xxxxxx' ]); // Make API calls $links = $client->links->list(); ``` Learn more about [how to get your API key](/api-reference/tokens). ## Native SDKs Dub offers native SDKs in some of the most popular programming languages: * [TypeScript SDK](/sdks/typescript) * [Python SDK](/sdks/python) * [Ruby SDK](/sdks/ruby) * [PHP SDK](/sdks/php) * [Go SDK](/sdks/go) You can find the full list of SDKs [here](/sdks/overview). ## Error Handling Dub API returns machine readable error codes, human readable error messages and a link to the docs for more information. Here is how an error response looks like: ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "doc_url": "https://dub.co/docs/api-reference/errors#not-found" } } ``` Here is a list of all error codes Dub API returns: * **Status:** 400 * **Problem:** The request is malformed, either missing required fields, using wrong datatypes, or being syntactically incorrect. * **Solution:** Check the request and make sure it is properly formatted. * **Status:** 401 * **Problem:** The request has not been applied because it lacks valid authentication credentials for the target resource. * **Solution:** Make sure you are using the correct API key or access token. * **Status:** 403 * **Problem:** The server understood the request, but is refusing to fulfill it because the client lacks proper permission. * **Solution:** Make sure you have the necessary permissions to access the resource. * **Status:** 404 * **Problem:** The server has not found anything matching the request URI. * **Solution:** Check the request and make sure the resource exists. * **Status:** 409 * **Problem:** Another resource already uses the same identifier. For example, workspace slug must be unique. * **Solution:** Change the identifier to a unique value. * **Status:** 410 * **Problem:** The invite has expired. * **Solution:** Generate a new invite. * **Status:** 422 * **Problem:** The server was unable to process the request because it contains invalid data. * **Solution:** Check the request and make sure input data is valid. * **Status:** 429 * **Problem:** The request has been rate limited. * **Solution:** Wait for a while and try again. * **Status:** 500 * **Problem:** The server encountered an unexpected condition that prevented it from fulfilling the request. * **Solution:** Try again later. If the problem persists, contact support. ## Pagination Dub's API supports pagination. This is useful when you have a large number of resources and you want to retrieve them in smaller chunks. These list API methods share a common set of parameters that allow you to control the number of items returned and the page number. For example, you can: * [retrieve a list of links](/api-reference/endpoint/retrieve-a-list-of-links) * [retrieve a list of domains](/api-reference/endpoint/retrieve-a-list-of-domains) * [retrieve a list of events](/api-reference/endpoint/retrieve-a-list-of-events) ### Parameters The page number to retrieve. By default, the first page is returned. The number of items to retrieve per page. The default value varies by endpoint. Maximum value is 100. The field to sort the results by. The order to sort the results by. Can be `asc` or `desc`. ### Example The following example demonstrates how to retrieve the first page of 10 links: ```bash cURL curl --request GET \ --url https://api.dub.co/links?page=1&pageSize=10 \ --header 'Authorization: Bearer ' ``` ```javascript Node.js const res = await dub.links.list({ page: 1, pageSize: 10, }); ``` ```python Python res = s.links.list(request={ "page": 1, "page_size": 10, }) ``` ```go Go request := operations.GetLinksRequest{ Page: dubgo.Float64(1), PageSize: dubgo.Float64(10), } ctx := context.Background() res, err := s.Links.List(ctx, request) ``` ```ruby Ruby req = ::OpenApiSDK::Operations::GetLinksRequest.new( page: 1, page_size: 10, ) res = s.links.list(req) ``` # Rate limits Source: https://dub.co/docs/api-reference/rate-limits Learn about Dub's API rate limits. Dub's API rate limiting is in conformance with the [IETF standard](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers): | Header Name | Description | | ----------------------- | ------------------------------------------------------------------------------- | | `X-RateLimit-Limit` | The maximum number of requests that the consumer is permitted to make per hour. | | `X-RateLimit-Remaining` | The number of requests remaining in the current rate limit window. | | `X-RateLimit-Reset` | The time at which the current rate limit window resets in UTC epoch seconds. | | `Retry-After` | The number of seconds to wait before retrying the request again. | Dub's API is capped at **60 requests per minute** per key on the Free plan, with elevated limits for [Pro plan](https://dub.co/help/article/pro-plan) and above. This is implemented to ensure a fair usage policy so that excessive use by a single user does not adversely affect the performance and usage of the API by others. You'll receive a `429 Too Many Requests` response code if the rate limit is exceeded. ## Rate limits by plan Depending on your Dub plan, you can expect the following rate limits: | Plan | Rate limit | | --------------------------------------- | ----------------------------------------------------------------------- | | Free | 60 requests per minute | | [Pro](https://dub.co/pricing) | 600 requests per minute | | [Business](https://dub.co/pricing) | 1,200 requests per minute | | [Advanced](https://dub.co/pricing) | 3,000 requests per minute | | [Enterprise](https://dub.co/enterprise) | Custom – [reach out to sales](https://dub.co/contact/sales) for details | ## How to comply with rate limits Here are some tips on how you can optimize your API setup to comply with our rate limits: ### 1. Bulk link creation If you need to create a lot of links within a short period of time, try our [bulk link creation endpoint](/api-reference/endpoint/bulk-create-links) instead (create up to 100 links in one API call) ```javascript Node.js await dub.links.createMany([ { url: "https://google.com", }, { url: "https://twitter.com", }, { url: "https://linkedin.com", }, ]); ``` ```python Python res = d.links.create_many(request=[ { url: "https://google.com", }, { url: "https://twitter.com", }, { url: "https://linkedin.com", }, ]); ``` ```go Go var request []operations.RequestBody = []operations.RequestBody{ operations.RequestBody{ URL: "https://google.com", }, operations.RequestBody{ URL: "https://twitter.com", }, operations.RequestBody{ URL: "https://linkedin.com", }, } ctx := context.Background() res, err := s.Links.CreateMany(ctx, request) ``` ```ruby Ruby s.links.create_many( ::OpenApiSDK::Operations::BulkCreateLinksRequest.new( request_body: [ ::OpenApiSDK::Operations::RequestBody.new( url: "https://google.com", ), ::OpenApiSDK::Operations::RequestBody.new( url: "https://twitter.com", ), ::OpenApiSDK::Operations::RequestBody.new( url: "https://linkedin.com", ), ] ) ) ``` ### 2. Fetch workspace-level analytics If you're using our [Analytics API](/api-reference/endpoint/retrieve-analytics), instead of retrieving the clicks count for every single link, try making a single API call to get workspace-level click analytics instead. ```javascript Node.js await dub.analytics.retrieve({ groupBy: "top_links", start: "4 hours ago", // we support natural language for start/end params }); ``` ```python Python res = d.analytics.retrieve(request={ "groupBy": "top_links", "start": "4 hours ago", // we support natural language for start/end params }) ``` ```go Go func main() { // Retrieve the timeseries analytics for the last 7 days for a link request := operations.RetrieveAnalyticsRequest{ GroupBy: "top_links", Start: "4 hours ago", // we support natural language for start/end params } ctx := context.Background() res, err := d.Analytics.Retrieve(ctx, request) if err != nil { log.Fatal(err) } if res.OneOf != nil { // handle response } } ``` ```ruby Ruby req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( group_by: ::OpenApiSDK::Operations::GroupBy::TOP_LINKS, start: "4 hours ago", // we support natural language for start/end params ) res = dub.analytics.retrieve(req) puts res.raw_response.body ``` ### 3. Leverage webhooks If you're expecting high volume, fast-changing data (e.g. thousands of clicks on your links per day) and would prefer to store the data on your end, we recommend using our [real-time webhooks feature](https://dub.co/blog/introducing-webhooks) instead. Webhook event logs [Webhooks](/concepts/webhooks/introduction) are *push-based*, meaning that Dub will send events to your webhook receiver endpoint when specific events occur, while a REST API is *pull-based*, meaning that you need to consistently poll the API endpoint to get real-time updates. Check out our [webhooks documentation](/concepts/webhooks/introduction) to learn more. # API keys Source: https://dub.co/docs/api-reference/tokens Learn how API keys work on Dub. API keys on Dub allow you to access your workspace programmatically. This is useful for integrating Dub into your application or with other tools and services. Each API key is tied to a specific workspace – meaning you can use it to access that workspace's resources without having to worry about "leaking" access to other workspaces. API keys on Dub follow the format: ```bash .env DUB_API_KEY=dub_xxxxxxxx ``` By default, you can use this key to perform any API request without restriction, so it must be stored securely in your app's server-side code (such as in an environment variable or credential management system). Don’t expose this key on a website. ## Create an API key You can create an API key by following these steps: Go to **Settings** > [**API Keys**](https://app.dub.co/settings/tokens) in your workspace. Workspace API keys on Dub Click on the "Create" button and select permissions you want to grant to the API key. Select between "You" and "Machine" to associate the API key with your account or a [machine user](#machine-users). * **You**: This API key is tied to your user and can make requests against the selected workspace. * **Machine**: A machine user will be added to your workspace, and an API key associated with that machine user will be created. Add new API key on Dub Click on the **Create API Key** button to create the key. Make sure to copy your API key and store it in a safe place. You won't be able to see it again. API Key created on Dub Now that you have your API key, you can use it to access your workspace's resources programmatically via SDKs or within any API request as a bearer token. ``` Authorization: Bearer dub_xxxx ``` We recommend creating API keys with the least privilege necessary to perform the required tasks. This helps to reduce the risk of unauthorized access to your workspace. ## API key permissions When creating a secret key, you can select the permissions it has, which will give the key access to certain (or all) resources on Dub. Here are the different permission options: | Permission | Description | | :------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **All permissions** | This API key will have full access to all resources. | | **Read only** | This API key will have read-only access to all resources. | | **Restricted** | This API key will have restricted access to some resources: | Depending on your use case, you might want to use one of these 3 options to limit the scope of the API key and improve security. When making API calls, if your API key has insufficient permissions, the error should tell you which permissions you need. You can only set permissions on Secret keys. Publishable keys only have access to certain endpoints, and cannot be restricted. ## Machine users On Dub, you can create API keys that are associated with a "Machine user". This is particularly helpful when you don't want to associate the API key with a particular user in your workspace, to avoid security risks in involving turnover or changes in project ownership. Machine users share the same permissions as the [owner role](https://dub.co/help/article/workspace-roles#owner-role) in a workspace. Make sure to only create machine users for trusted applications. Creating an API key associated with a machine user on Dub These machine users will show up on your workspace's **People** tab, but will not contribute to your workspace's user count. Machine user on Dub If you delete an API key associated with a machine user, the machine user will be deleted. Vice versa, if you delete a machine user, their corresponding API key will be deleted as well. # Device data Source: https://dub.co/docs/concepts/analytics/device Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription or higher. Device data allows you to analyze how users interact with your links across different devices, browsers, and operating systems. ## Device analytics The top devices by event count, including device names. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "devices", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "devices", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'devices', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("devices"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "devices", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## Browser analytics The top browsers by event count, including browser names. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "browsers", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "browsers", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'browsers', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("browsers"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "browsers", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## Operating system analytics The top operating systems by event count, including OS names. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "os", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "os", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'os', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("os"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "os", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` # Introduction Source: https://dub.co/docs/concepts/analytics/introduction Learn how to use Dub's Analytics API to build real-time analytics dashboards for your links. Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription or higher. Dub's [Analytics API](/api-reference/endpoint/retrieve-analytics) allows you to build real-time analytics dashboards for your links that lives directly inside your application. ## Fundamentals At a high level, Dub's Analytics API allows you to retrieve data about your links by event type and group by different dimensions. Dub's Analytics API supports the following `event` types: * `clicks` – the number of clicks on a link * `leads` – the number of leads generated from a link * `sales` – the number of sales generated from a link Each of these events can be aggregated in different ways by using the `groupBy` parameter: The total number of events over the specified time period. Example response: ```json { "clicks": 100, "leads": 5, "sales": 2, "saleAmount": 5000 } ``` The number of events over a given time interval, broken down by time periods. Example response: ```json [ { "start": "2024-01-01T00:00:00.000Z", "clicks": 10, "leads": 1, "sales": 0, "saleAmount": 0 }, { "start": "2024-01-02T00:00:00.000Z", "clicks": 15, "leads": 2, "sales": 1, "saleAmount": 2500 } ] ``` The top links by event count, including link metadata. Example response: ```json [ { "id": "clux0rgak00011...", "domain": "dub.co", "key": "github", "shortLink": "https://dub.co/github", "url": "https://github.com", "title": "GitHub", "createdAt": "2024-01-01T00:00:00.000Z", "clicks": 50, "leads": 3, "sales": 1, "saleAmount": 2500 } ] ``` The top countries by event count, using ISO 3166-1 alpha-2 country codes. Example response: ```json [ { "country": "US", "region": "*", "city": "*", "clicks": 30, "leads": 2, "sales": 1, "saleAmount": 2500 } ] ``` The top cities by event count, including city names and ISO 3166-1 alpha-2 country codes. Example response: ```json [ { "country": "US", "region": "CA", "city": "San Francisco", "clicks": 20, "leads": 1, "sales": 1, "saleAmount": 2500 } ] ``` The top regions by event count, including region codes and ISO 3166-1 alpha-2 country codes. Example response: ```json [ { "country": "US", "region": "CA", "city": "*", "clicks": 25, "leads": 2, "sales": 1, "saleAmount": 2500 } ] ``` The top continents by event count, using 2-letter ISO continent codes. Example response: ```json [ { "continent": "NA", "clicks": 40, "leads": 3, "sales": 2, "saleAmount": 5000 } ] ``` The top devices by event count, including device names. Example response: ```json [ { "device": "iPhone", "clicks": 35, "leads": 2, "sales": 1, "saleAmount": 2500 } ] ``` The top browsers by event count, including browser names. Example response: ```json [ { "browser": "Chrome", "clicks": 45, "leads": 3, "sales": 2, "saleAmount": 5000 } ] ``` The top operating systems by event count, including OS names. Example response: ```json [ { "os": "iOS", "clicks": 30, "leads": 2, "sales": 1, "saleAmount": 2500 } ] ``` The top referrers by event count, including referrer names. Example response: ```json [ { "referer": "Google", "clicks": 25, "leads": 2, "sales": 1, "saleAmount": 2500 } ] ``` The top referrer URLs by event count, including full URLs. Example response: ```json [ { "refererUrl": "https://www.google.com", "clicks": 20, "leads": 1, "sales": 1, "saleAmount": 2500 } ] ``` The top UTM sources by event count. Example response: ```json [ { "utm_source": "newsletter", "clicks": 25, "leads": 2, "sales": 1, "saleAmount": 2500 } ] ``` The top UTM mediums by event count. Example response: ```json [ { "utm_medium": "email", "clicks": 20, "leads": 1, "sales": 1, "saleAmount": 2500 } ] ``` The top UTM campaigns by event count. Example response: ```json [ { "utm_campaign": "summer_sale", "clicks": 30, "leads": 2, "sales": 1, "saleAmount": 2500 } ] ``` The top UTM terms by event count. Example response: ```json [ { "utm_term": "discount", "clicks": 15, "leads": 1, "sales": 1, "saleAmount": 2500 } ] ``` The top UTM contents by event count. Example response: ```json [ { "utm_content": "banner", "clicks": 20, "leads": 1, "sales": 1, "saleAmount": 2500 } ] ``` ## Example queries Here are some examples of how to retrieve data using Dub's [Analytics API](/api-reference/endpoint/retrieve-analytics): * [Total event count](#total-event-count) * [Timeseries data](#timeseries-data) * [Top links by event](#top-links-by-event) ### Total event count ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "count", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "count", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'count', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("count"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "count", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ### Timeseries data ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "timeseries", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "timeseries", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'timeseries', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("timeseries"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "timeseries", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ### Top links by event ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "top_links", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "top_links", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'top_links', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("top_links"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "top_links", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## Example apps With Dub's [Analytics API](/api-reference/endpoint/retrieve-analytics), you can build user-facing analytics dashboards with the real-time click and conversion data for your links. Here are some open-source examples of how you can use the Analytics API to build your own custom analytics dashboards: Programmatically shorten links and fetch real-time click analytics with Dub How Cap.so fetches real-time click analytics for their recording links And here's another [real-world example](https://x.com/meetassembly/status/1901691081579794505) of a custom analytics dashboard built with the Analytics API: Assembly link analytics # Location data Source: https://dub.co/docs/concepts/analytics/location Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription or higher. Location data allows you to analyze how users interact with your links across different geographical locations. ## Country analytics The top countries by event count, including ISO 3166-1 alpha-2 country codes. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "countries", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "countries", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'countries', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("countries"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "countries", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## City analytics The top cities by event count, including city names and their corresponding region codes + ISO 3166-1 alpha-2 country codes. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "cities", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "cities", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'cities', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("cities"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "cities", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## Continent analytics The top continents by event count, using 2-letter ISO continent codes. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "continents", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "continents", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'continents', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("continents"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "continents", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## Region analytics The top regions by event count, including region codes and ISO 3166-1 alpha-2 country codes. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "regions", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "regions", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'regions', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("regions"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "regions", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` # Referrers data Source: https://dub.co/docs/concepts/analytics/referrers Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription or higher. Referrers data allows you to analyze where your link traffic is coming from, including both the referrer domain and the full referrer URL. ## Referrer domain analytics The top referrers by event count, including referrer names. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "referrers", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "referrers", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'referrers', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("referrers"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "referrers", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## Referrer URL analytics The top referrer URLs by event count, including full URLs. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "referer_urls", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "referer_urls", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'referer_urls', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("referer_urls"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "referer_urls", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` # Tags data Source: https://dub.co/docs/concepts/analytics/tags Learn how to retrieve analytics data on Dub by tags ## Filter analytics by tags You can filter analytics data by tags by passing the `tagIds` parameter to the `retrieve` function. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "top_links", tagIds: ["tag_12345", "tag_67890"], interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "top_links", "tagIds": ["tag_12345", "tag_67890"], "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'top_links', tagIds: ['tag_12345', 'tag_67890'], interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("top_links"), TagIDs: []string{"tag_12345", "tag_67890"}, Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "top_links", tagIds: ["tag_12345", "tag_67890"], interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## Top tags by event This feature is coming soon. If you'd like early access, please [contact us](https://dub.co/contact/support). ## Tag analytics The top links by event count, filtered by tags. # UTM data Source: https://dub.co/docs/concepts/analytics/utm Analytics endpoints require a [Pro plan](https://dub.co/pricing) subscription or higher. UTM data allows you to analyze the effectiveness of your marketing campaigns by tracking the source, medium, campaign, term, and content parameters. ## UTM source analytics The top UTM sources by event count. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "utm_sources", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "utm_sources", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'utm_sources', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("utm_sources"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "utm_sources", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## UTM medium analytics The top UTM mediums by event count. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "utm_mediums", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "utm_mediums", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'utm_mediums', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("utm_mediums"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "utm_mediums", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## UTM campaign analytics The top UTM campaigns by event count. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "utm_campaigns", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "utm_campaigns", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'utm_campaigns', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("utm_campaigns"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "utm_campaigns", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## UTM term analytics The top UTM terms by event count. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "utm_terms", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "utm_terms", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'utm_terms', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("utm_terms"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "utm_terms", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` ## UTM content analytics The top UTM contents by event count. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.analytics.retrieve({ event: "clicks", groupBy: "utm_contents", linkId: "clux0rgak00011...", interval: "30d", }); ``` ```python Python from dub import Dub with Dub( token="DUB_API_KEY", ) as d_client: res = d_client.analytics.retrieve(request={ "event": "clicks", "groupBy": "utm_contents", "linkId": "clux0rgak00011...", "interval": "30d", }) print(res) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder() ->setSecurity('DUB_API_KEY') ->build(); $request = new Operations\RetrieveAnalyticsRequest( event: 'clicks', groupBy: 'utm_contents', linkId: 'clux0rgak00011...', interval: '30d', ); $response = $sdk->analytics->retrieve( request: $request ); if ($response->oneOf !== null) { // handle response } ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity("DUB_API_KEY"), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ Event: dubgo.String("clicks"), GroupBy: dubgo.String("utm_contents"), LinkID: dubgo.String("clux0rgak00011..."), Interval: dubgo.String("30d"), }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new( security: ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ), ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( event: "clicks", groupBy: "utm_contents", linkId: "clux0rgak00011...", interval: "30d", ) res = s.analytics.retrieve(req) if ! res.one_of.nil? # handle response end ``` # Bulk operations Source: https://dub.co/docs/concepts/links/bulk-operations Learn how to perform bulk operations on links. Dub allows you to perform bulk operations on links. This is particularly useful when you need to [create](/api-reference/endpoint/bulk-create-links), [update](/api-reference/endpoint/bulk-update-links), or [delete](/api-reference/endpoint/bulk-delete-links) multiple links at once without having to make multiple API requests. ## Bulk create links Bulk create allows you to create up to 100 links at once. Bulk link creation does not support [custom link previews](https://dub.co/help/article/custom-link-previews). Also, [webhook events](/concepts/webhooks/introduction) will not be triggered when using bulk link creation. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.links.createMany([ { url: "https://google.com", }, { url: "https://google.uk", }, ]); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) } res, err := s.Links.CreateMany(ctx, []operations.RequestBody{ operations.RequestBody{ URL: "https://google.com", }, operations.RequestBody{ URL: "https://google.uk", }, }) ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = dub.links.create_many(request=[ { "url": "https://google.com", }, { "url": "https://google.uk", }, ]) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = [ ::OpenApiSDK::Operations::RequestBody.new( url: "https://google.com", ), ::OpenApiSDK::Operations::RequestBody.new( url: "https://example.uk" ), ] res = s.links.create_many(req) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = [ new Operations\RequestBody( url: 'https://google.com', ), new Operations\RequestBody( url: 'https://google.uk', ), ]; $response = $sdk->links->createMany( request: $request ); ``` ```bash cURL curl --request POST \ --url https://api.dub.co/links/bulk \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' --data '[ { "url": "https://google.com" }, { "url": "https://google.uk" } ]' ``` Check out the [full API reference for the link bulk creation endpoint](/api-reference/endpoint/bulk-create-links). ## Bulk update links Bulk update allows you to modify up to 100 links simultaneously **with the same data**. Some potential use cases: * Tagging multiple links at once * Setting the same expiration date for multiple links * Updating UTM parameters for multiple links You cannot update the domain or key of a link with this endpoint. Also, [webhook events](/concepts/webhooks/introduction) will not be triggered when using bulk link updates ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.links.updateMany({ linkIds: ["clux0rgak00011...", "clux0rgak00022..."], data: { utm_source: "facebook", utm_medium: "cpc", }, }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.UpdateMany(ctx, operations.BulkUpdateLinksRequestBody{ LinkIds: []string{ "clux0rgak00011...", "clux0rgak00022...", }, Data: map[string]string{ "utm_source": "facebook", "utm_medium": "cpc", }, }) } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = dub.links.update_many(request={ "link_ids": [ "clux0rgak00011...", "clux0rgak00022...", ], "data": { "utm_source": "facebook", "utm_medium": "cpc", }, }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::BulkUpdateLinksRequestBody.new( link_ids: [ "clux0rgak00011...", "clux0rgak00022...", ], data: { "utm_source": "facebook", "utm_medium": "cpc", }, ) res = s.links.update_many(req) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $response = $sdk->links->updateMany( linkIds: [ 'clux0rgak00011...', 'clux0rgak00022...', ], data: { "utm_source": "facebook", "utm_medium": "cpc", }, ); ``` ```bash cURL curl --request PATCH \ --url https://api.dub.co/links/bulk \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{"link_ids": ["clux0rgak00011...", "clux0rgak00022..."], "data": {"utm_source": "facebook", "utm_medium": "cpc"}}' ``` Check out the [full API reference for the link bulk update endpoint](/api-reference/endpoint/bulk-update-links). ## Bulk delete links With bulk delete, you can delete up to 100 links at once. This is a destructive action and cannot be undone. Proceed with caution. Also, [webhook events](/concepts/webhooks/introduction) will not be triggered when using this endpoint. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.links.deleteMany({ linkIds: ["clux0rgak00011...", "clux0rgak00022..."], }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.DeleteMany(ctx, operations.BulkDeleteLinksRequest{ LinkIds: []string{ "clux0rgak00011...", "clux0rgak00022...", }, }) } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = dub.links.delete_many(request={ "link_ids": [ "clux0rgak00011...", "clux0rgak00022...", ], }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::BulkDeleteLinksRequest.new( link_ids: [ "clux0rgak00011...", "clux0rgak00022...", ], ) res = s.links.delete_many(req) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $response = $sdk->links->deleteMany( linkIds: [ 'clux0rgak00011...', 'clux0rgak00022...', ] ); ``` ```bash cURL curl --request DELETE \ --url https://api.dub.co/links/bulk?linkIds=clux0rgak00011... \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` Check out the [full API reference for the link bulk delete endpoint](/api-reference/endpoint/bulk-delete-links). # Introduction Source: https://dub.co/docs/concepts/links/introduction Learn how to use Dub to programmatically create, update, and delete links at scale. Links are the bread and butter of [Dub](https://dub.co). Everything on Dub starts with a link. Whether you're creating: * a handful of links for your marketing campaign * hundreds of links for your [affiliate program](/partners/quickstart) * thousands of links, [programmatically](/api-reference/endpoint/bulk-create-links), for your SMS campaign In this guide, we'll cover the link model, how to create links, and more. ## The link model The link model consists of the following properties: | Property | Description | Example | | ----------- | :-------------------------------------------------------- | :------------------------------ | | `id` | The unique identifier of the link (prefixed with `link_`) | `link_eBKA4MT44XnI17hYLchkjUOd` | | `url` | The destination URL of the link | `https://dub.co/home` | | `shortLink` | The shortened version of the link (including https) | `https://dub.link/claim` | | `domain` | The domain of the link | `dub.link` | | `key` | The short link slug | `claim` | For more advanced features like [custom link previews](https://dub.co/help/article/custom-link-previews), [conversion tracking](/conversions/quickstart), and more, see the full list of properties [here](/api-reference/endpoint/create-a-link). You can use the various [Dub SDKs](/sdks/overview) to programmatically manage your links. ## Create a link The `url` field, representing the destination URL, is the sole mandatory parameter required for the creation of a new short link. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const link = await dub.links.create({ url: "https://google.com", }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{ URL: "https://google.com", }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = d.links.create(request={ "url": "https://google.com", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new( url: "https://google.com", ) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\CreateLinkRequestBody( url: 'https://google.com', ); $response = $sdk->links->create( request: $request ); ``` ```bash cURL {5} curl --request POST \ --url https://api.dub.co/links \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{"url": "https://google.com"}' ``` Check out the [full API reference for the link creation endpoint](/api-reference/endpoint/create-a-link). ## Update a link An existing link can be updated by providing the `id` to the `update` method. This method returns the updated link as a response. You can use either the `linkId` or an `externalId` prefixed with `ext_` which is a unique identifier for the link in your own database to associate it with the link in Dub's system. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const link = await dub.links.update("link_eBKA4MT44XnI17hYLchkjUOd", { url: "https://www.google.uk", // new URL }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.Update(ctx, "link_eBKA4MT44XnI17hYLchkjUOd", &operations.UpdateLinkRequestBody{ URL: "https://www.google.uk", // new URL }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = dub.links.update(link_id="link_eBKA4MT44XnI17hYLchkjUOd", request_body={ "url": "https://www.google.uk", // new URL }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) res = s.links.update(link_id="link_eBKA4MT44XnI17hYLchkjUOd", request_body={ "url": "https://www.google.uk", // new URL }) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $requestBody = new Operations\UpdateLinkRequestBody( url: 'https://www.google.uk', // new URL ); $response = $sdk->links->update( linkId: 'link_eBKA4MT44XnI17hYLchkjUOd', requestBody: $requestBody ); ``` ```bash cURL curl --request PATCH \ --url https://api.dub.co/links/link_eBKA4MT44XnI17hYLchkjUOd \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{"url": "https://www.google.uk"}' ``` Check out the [full API reference for the link update endpoint](/api-reference/endpoint/update-a-link). ## Upsert a link Upserting a link is a combination of creating and updating a link. If a link with the same URL already exists, return it (or update it if there are any changes). Otherwise, a new link will be created. This allows you to use the upsert method without the necessity of checking for the link's existence beforehand. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const link = await dub.links.upsert({ url: "https://google.com", // will always be the same short link }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.Upsert(ctx, &operations.UpsertLinkRequestBody{ URL: "https://google.com", }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = d.links.upsert(request={ "url": "https://google.com", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::UpsertLinkRequestBody.new( url: "https://google.com", ) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\UpsertLinkRequestBody( url: 'https://google.com', ); $response = $sdk->links->upsert( request: $request ); ``` ```bash cURL curl --request POST \ --url https://api.dub.co/links/upsert \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{"url": "https://google.com"}' ``` Check out the [full API reference for the link upsert endpoint](/api-reference/endpoint/upsert-a-link). # Organizing links Source: https://dub.co/docs/concepts/links/organization Learn how to associate links with users, campaigns, teams, and other entities within your system. When creating links programmatically with Dub, you might want a way to associate them with a user or other identifiers in your system. There are a few ways to do this, depending on your data structure: | Method | Type | Description | Use case | | --------------------------- | ------------ | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | [External ID](#external-id) | One-to-one | A unique identifier for a link within your system. | Associating referral links with users in your system. | | [Tenant ID](#tenant-id) | One-to-many | The ID of the tenant that created the link. | Grouping all links created by a user/team in your system. | | [Tags](#tags) | Many-to-many | Grouping links by tags | Organizing links by campaign / user / various for flexible, multi-dimensional filtering and reporting | ## External ID In certain scenarios, it is essential to identify links within your system. For instance, you may need to associate a link with a user without storing the Dub link ID directly in your database (e.g. for referral links). The `externalId` field serves this purpose effectively. It acts as a unique identifier within your database, allowing you to associate it with a corresponding link in Dub's system. Dub allows you to create links using an `externalId` and subsequently retrieve them by the same identifier. `externalId` should be a unique value across your workspace. Trying to create a link with an externalId that already exists will result in a [`409` conflict error](/api-reference/introduction#conflict) error. ### Create link with an externalId Here is an example of how to create a link with an `externalId`: ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const link = await dub.links.create({ url: "https://google.com", externalId: "12345", }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{ URL: "https://google.com", ExternalId: "12345", }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = d.links.create(request={ "url": "https://google.com", "external_id": "12345", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new( url: "https://google.com", external_id: "12345", ) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\CreateLinkRequestBody( url: 'https://google.com', externalId: '12345', ); $response = $sdk->links->create( request: $request ); ``` ```bash cURL curl --request POST \ --url https://api.dub.co/links \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "url": "https://google.com", "external_id": "12345" }' ``` ### Retrieve link by externalId Let's see how to retrieve a link by its `externalId`: ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const link = await dub.links.get({ externalId: "12345", }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.Get(ctx, operations.GetLinkInfoRequest{ ExternalID: dubgo.String("12345"), }) } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = d.links.get(request={ "external_id": "12345", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::GetLinkInfoRequest.new( external_id: "12345", ) res = s.links.get(req) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $response = $sdk->links->get( externalId: '12345' ); ``` ```bash cURL curl --request GET \ --url https://api.dub.co/links/info?external_id=12345 \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` ### Update link by externalId In addition to updating a link by its `linkId`, you can also update a link by its `externalId`. Make sure to prefix the `externalId` with `ext_`. For example, if your `externalId` is `12345`, you should pass `ext_12345`. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const link = await dub.links.update("ext_12345", { url: "https://www.google.uk", // new URL }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.Update(ctx, "ext_12345", &operations.UpdateLinkRequestBody{ URL: "https://www.google.uk", // new URL }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = dub.links.update(link_id="ext_12345", request_body={ "url": "https://www.google.uk", // new URL }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) res = s.links.update(link_id="ext_12345", request_body={ "url": "https://www.google.uk", // new URL }) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $requestBody = new Operations\UpdateLinkRequestBody( url: 'https://www.google.uk', // new URL ); $response = $sdk->links->update( linkId: 'ext_12345', requestBody: $requestBody ); ``` ```bash cURL curl --request PATCH \ --url https://api.dub.co/links/ext_12345 \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{"url": "https://www.google.uk"}' ``` ### Retrieve analytics by externalId You can also retrieve analytics for a link by its `externalId`. This is helpful for fetching the analytics for a given link using the unique identifier from your system. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const analytics = await dub.analytics.retrieve({ externalId: "ext_12345", }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ ExternalID: dubgo.String("ext_12345"), }) } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = dub.analytics.retrieve(request={ "external_id": "ext_12345", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( external_id: "ext_12345", ) res = s.analytics.retrieve(req) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\RetrieveAnalyticsRequest( externalId: "ext_12345", ); $response = $sdk->analytics->retrieve( request: $request ); ``` ```bash cURL curl --request GET \ --url https://api.dub.co/analytics?external_id=ext_12345 \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` ## Tenant ID The ID of the tenant that created the link inside your system. If set, it can be used to fetch all links for a tenant. This is useful if you have a system that lets your users create their own links, and you want to group them on a tenant level. ### Create link with tenantId Let's see how to create a link with a tenant ID: ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const link = await dub.links.create({ url: "https://google.com", tenantId: "12345", }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{ URL: "https://google.com", TenantId: "12345", }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = d.links.create(request={ "url": "https://google.com", "tenant_id": "12345", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new( url: "https://google.com", tenant_id: "12345", ) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\CreateLinkRequestBody( url: 'https://google.com', tenantId: '12345', ); $response = $sdk->links->create( request: $request ); ``` ```bash cURL curl --request POST \ --url https://api.dub.co/links \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "url": "https://google.com", "external_id": "12345" }' ``` ### Retrieve links by tenantId Here is how to retrieve links by tenant ID: ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.links.list({ tenantId: "12345", }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.List(ctx, operations.GetLinksRequest{ TenantId: dubgo.String("12345"), }) } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = s.links.list(request={ "tenant_id": "12345", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::GetLinksRequest.new( tenant_id: "12345", ) res = s.links.list(req) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\GetLinksRequest( tenantId: "12345", ); $responses = $sdk->links->list( request: $request ); ``` ```bash cURL curl --request GET \ --url https://api.dub.co/links?tenantId=12345 \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` ### Retrieve analytics by tenantId You can retrieve analytics by tenantId by passing the `tenantId` prop. This is helpful for fetching the analytics for all the links under a specific tenant, or the total analytics for a tenant. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const analytics = await dub.analytics.retrieve({ tenantId: "12345", }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ TenantId: dubgo.String("12345"), }) } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = dub.analytics.retrieve(request={ "tenant_id": "12345", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( tenant_id: "12345", ) res = s.analytics.retrieve(req) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\RetrieveAnalyticsRequest( tenantId: "12345", ); $response = $sdk->analytics->retrieve( request: $request ); ``` ```bash cURL curl --request GET \ --url https://api.dub.co/analytics?tenantId=12345 \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` *** ## Tags Tags are a great way to organize your links on Dub. With tags, you can: * Organize your links by campaigns, clients, or any other categories you can think of. * [Filter your links by tags](#retrieve-links-by-tags) and get a shareable link to the filtered results. * [Filter your analytics by tags](#retrieve-analytics-by-tags) to see how your campaigns are performing. ### Create link with tags You can use either pass the tag ID or tag name to create a link with tags. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const link = await dub.links.create({ url: "https://example.com", tagIds: ["clux0rgak00011..."], }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.Create(ctx, &operations.CreateLinkRequestBody{ URL: "https://example.com", TagIds: []string{"clux0rgak00011..."}, }) if err != nil { log.Fatal(err) } if res != nil { // handle response } } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = d.links.create(request={ "url": "https://example.com", "tag_ids": ["clux0rgak00011..."], }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::CreateLinkRequestBody.new( url: "https://example.com", tag_ids: ["clux0rgak00011..."], ) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\CreateLinkRequestBody( url: 'https://example.com', tagIds: ['clux0rgak00011...'], ); $response = $sdk->links->create( request: $request ); ``` ```bash cURL curl --request POST \ --url https://api.dub.co/links \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data '{ "url": "https://example.com", "tagIds": ["clux0rgak00011..."] }' ``` ### Retrieve links by tags You can retrieve links by tag by tags. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const result = await dub.links.list({ tagNames: ["tag1"], }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Links.List(ctx, operations.GetLinksRequest{ TagNames: []string{"tag1"}, }) } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = s.links.list(request={ "tag_names": ["tag1"], }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::GetLinksRequest.new( tag_names: ["tag1"], ) res = s.links.list(req) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\GetLinksRequest( tagNames: ["tag1"], ); $responses = $sdk->links->list( request: $request ); ``` ```bash cURL curl --request GET \ --url https://api.dub.co/links?tagNames=tag1 \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` ### Retrieve analytics by tags You can retrieve analytics for a tag (or multiple tags) by passing the `tagIds` prop. This is helpful for fetching the analytics for all the links under a specific tag, or the total analytics for a tag (or multiple tags). ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const analytics = await dub.analytics.retrieve({ tagIds: ["tag_xxx"], }); ``` ```go Go package main import( "context" dubgo "github.com/dubinc/dub-go" "github.com/dubinc/dub-go/models/operations" "log" "os" ) func main() { ctx := context.Background() s := dubgo.New( dubgo.WithSecurity(os.Getenv("DUB_API_KEY")), ) res, err := s.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ TagIds: []string{"tag_xxx"}, }) } ``` ```python Python import os import dub from dub.models import operations d = dub.Dub( token=os.environ['DUB_API_KEY'], ) res = dub.analytics.retrieve(request={ "tag_ids": ["tag_xxx"], }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( tag_ids: ["tag_xxx"], ) res = s.analytics.retrieve(req) ``` ```php PHP declare(strict_types=1); require 'vendor/autoload.php'; use Dub; use Dub\Models\Operations; $sdk = Dub\Dub::builder()->setSecurity('DUB_API_KEY')->build(); $request = new Operations\RetrieveAnalyticsRequest( tagIds: ["tag_xxx"], ); $response = $sdk->analytics->retrieve( request: $request ); ``` ```bash cURL curl --request GET \ --url https://api.dub.co/analytics?tagIds=tag_xxx \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` # Event types Source: https://dub.co/docs/concepts/webhooks/event-types List of available webhook events you can listen to along with their payload examples Webhooks are a great way to get real-time notifications on events that happen in your Dub workspace. Webhooks on Dub follow the following format: ```json webhook-payload.json { "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX", // The event ID "event": "link.created", // The event type "createdAt": "2024-08-26T16:41:52.346Z", // The timestamp of when the event was created "data": { // Event payload } } ``` There are two types of webhook events you can listen to: * [**Workspace-level events**](#workspace-level-events) * [**Link-level events**](#link-level-events) ## Workspace-level events These events are triggered in the context of your entire workspace: * [`link.created`](#link-created) * [`link.updated`](#link-updated) * [`link.deleted`](#link-deleted) * [`lead.created`](#lead-created) * [`sale.created`](#sale-created) * [`partner.enrolled`](#partner-enrolled) ### `link.created` This event is triggered when a [new link is created](/api-reference/endpoint/create-a-link) in your Dub workspace. The event payload contains the created link's details. Here's an example payload: ```json link.created { "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX", "event": "link.created", "createdAt": "2024-08-26T16:41:52.346Z", "data": { "id": "cm0b87844000dismqhkviju54", "domain": "dub.sh", "key": "sOvvXDT", "externalId": null, "url": "https://github.com/stack-auth/stack", "trackConversion": false, "archived": false, "expiresAt": null, "expiredUrl": null, "password": null, "proxy": false, "title": null, "description": null, "image": null, "video": null, "rewrite": false, "doIndex": false, "ios": null, "android": null, "geo": null, "publicStats": false, "tagId": null, "tags": [], "comments": null, "shortLink": "https://dub.sh/sOvvXDT", "qrCode": "https://api.dub.co/qr?url=https://dub.sh/sOvvXDT?qr=1", "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "userId": "cm022rkcw0000ikt14mscg9sg", "workspaceId": "ws_cm022sis60003ikt1syy7kfhl", "clicks": 0, "lastClicked": null, "leads": 0, "sales": 0, "saleAmount": 0, "createdAt": "2024-08-26T16:41:52.084Z", "updatedAt": "2024-08-26T16:41:52.084Z", "projectId": "cm022sis60003ikt1syy7kfhl" } } ``` ### `link.updated` This event is triggered when a [link is updated](/api-reference/endpoint/update-a-link) in your Dub workspace. The event payload contains the updated link's details. Here's an example payload: ```json link.updated { "id": "event_KleiO4HBwZFbO1vZLWIPZ2AtX", "event": "link.updated", "createdAt": "2024-08-26T16:41:52.346Z", "data": { "id": "cm0b87844000dismqhkviju54", "domain": "dub.sh", "key": "sOvvXDT", "externalId": null, "url": "https://github.com/stack-auth/stack", "trackConversion": false, "archived": false, "expiresAt": null, "expiredUrl": null, "password": null, "proxy": false, "title": null, "description": null, "image": null, "video": null, "rewrite": false, "doIndex": false, "ios": null, "android": null, "geo": null, "publicStats": false, "tagId": null, "tags": [], "comments": null, "shortLink": "https://dub.sh/sOvvXDT", "qrCode": "https://api.dub.co/qr?url=https://dub.sh/sOvvXDT?qr=1", "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "userId": "cm022rkcw0000ikt14mscg9sg", "workspaceId": "ws_cm022sis60003ikt1syy7kfhl", "clicks": 0, "lastClicked": null, "leads": 0, "sales": 0, "saleAmount": 0, "createdAt": "2024-08-26T16:41:52.084Z", "updatedAt": "2024-08-26T16:41:52.084Z", "projectId": "cm022sis60003ikt1syy7kfhl" } } ``` ### `link.deleted` This event is triggered when a [link is deleted](/api-reference/endpoint/delete-a-link) in your Dub workspace. The event payload contains the deleted link's details. Here's an example payload: ```json link.deleted { "id": "evt_KleiO4HBwZFbO1vZLWIPZ2AtX", "event": "link.deleted", "createdAt": "2024-08-26T16:41:52.346Z", "data": { "id": "cm0b87844000dismqhkviju54", "domain": "dub.sh", "key": "sOvvXDT", "externalId": null, "url": "https://github.com/stack-auth/stack", "trackConversion": false, "archived": false, "expiresAt": null, "expiredUrl": null, "password": null, "proxy": false, "title": null, "description": null, "image": null, "video": null, "rewrite": false, "doIndex": false, "ios": null, "android": null, "geo": null, "publicStats": false, "tagId": null, "tags": [], "comments": null, "shortLink": "https://dub.sh/sOvvXDT", "qrCode": "https://api.dub.co/qr?url=https://dub.sh/sOvvXDT?qr=1", "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "userId": "cm022rkcw0000ikt14mscg9sg", "workspaceId": "ws_cm022sis60003ikt1syy7kfhl", "clicks": 0, "lastClicked": null, "leads": 0, "sales": 0, "saleAmount": 0, "createdAt": "2024-08-26T16:41:52.084Z", "updatedAt": "2024-08-26T16:41:52.084Z", "projectId": "cm022sis60003ikt1syy7kfhl" } } ``` ### `lead.created` This event is triggered when a [new lead is created](/api-reference/endpoint/track-lead) via [Dub Conversions](/conversions/quickstart). The event payload contains the following: * `eventName`: The name of the event that was tracked. * `customer`: Details about the customer that signed up. * `click`: Details about the click event that led to the lead event. * `link`: Details about the referral link that the lead event is associated with. Here's an example payload: ```json lead.created { "id": "evt_P343bmyae40ALQYr5HT4vRXRd", "event": "lead.created", "createdAt": "2024-08-30T09:53:50.343Z", "data": { "eventName": "Sign up", "customer": { "id": "oU5P0SqI8fpwx5bxw1", "name": "John", "email": "john@example.com", "avatar": "https://example.com/john.jpeg" }, "click": { "id": "d0UtZqE0BZuBPrJS", "url": "https://github.com/dubinc/dub", "ip": "63.141.57.109", "continent": "NA", "country": "US", "city": "San Francisco", "device": "Desktop", "browser": "Chrome", "os": "Mac OS", "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", "bot": false, "qr": false, "referer": "(direct)" }, "link": { "id": "cm0faqkyn0001txvfwjfeq7gl", "domain": "dub.sh", "key": "79ys3WA", "externalId": null, "url": "https://github.com/dubinc/dub", "trackConversion": true, "archived": false, "expiresAt": null, "expiredUrl": null, "password": null, "proxy": false, "title": null, "description": null, "image": null, "video": null, "rewrite": false, "doIndex": false, "ios": null, "android": null, "geo": null, "publicStats": false, "comments": null, "shortLink": "https://dub.sh/79ys3WA", "qrCode": "https://api.dub.co/qr?url=https://dub.sh/79ys3WA?qr=1", "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "userId": "cm022rkcw0000ikt14mscg9sg", "workspaceId": "ws_cm022sis60003ikt1syy7kfhl", "clicks": 10, "lastClicked": "2024-08-30T07:45:09.000Z", "leads": 5, "sales": 0, "saleAmount": 0, "createdAt": "2024-08-29T13:03:59.098Z", "updatedAt": "2024-08-30T09:53:49.505Z" } } } ``` ### `sale.created` This event is triggered when a [new sale is tracked](/api-reference/endpoint/track-sale) via [Dub Conversions](/conversions/quickstart). The event payload contains the following: * `eventName`: The name of the event that was tracked. * `customer`: Details about the customer that made the purchase. * `click`: Details about the click event that led to the sale event. * `link`: Details about the referral link that the sale event is associated with. Here's an example payload: ```json sale.created { "id": "evt_WHjyHhqsfYOrlJOOVJSoHXysD", "event": "sale.created", "createdAt": "2024-08-30T09:57:51.245Z", "data": { "eventName": "Purchased", "customer": { "id": "cm0gjdvr20001dkbha2n9gt2b", "name": "John", "email": "john@example.com", "avatar": "https://example.com/john.jpeg" }, "click": { "id": "d0UtZqE0BZuBPrJS", "url": "https://github.com/dubinc/dub", "ip": "63.141.57.109", "continent": "NA", "country": "US", "city": "San Francisco", "device": "Desktop", "browser": "Chrome", "os": "Mac OS", "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", "bot": false, "qr": false, "referer": "(direct)" }, "link": { "id": "cm0faqkyn0001txvfwjfeq7gl", "domain": "dub.sh", "key": "79ys3WA", "externalId": null, "url": "https://github.com/dubinc/dub", "trackConversion": true, "archived": false, "expiresAt": null, "expiredUrl": null, "password": null, "proxy": false, "title": null, "description": null, "image": null, "video": null, "rewrite": false, "doIndex": false, "ios": null, "android": null, "geo": null, "publicStats": false, "comments": null, "shortLink": "https://dub.sh/79ys3WA", "qrCode": "https://api.dub.co/qr?url=https://dub.sh/79ys3WA?qr=1", "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "userId": "cm022rkcw0000ikt14mscg9sg", "workspaceId": "ws_cm022sis60003ikt1syy7kfhl", "clicks": 10, "lastClicked": "2024-08-30T07:45:09.000Z", "leads": 5, "sales": 1, "saleAmount": 20000, "createdAt": "2024-08-29T13:03:59.098Z", "updatedAt": "2024-08-30T09:57:50.891Z" }, "sale": { "amount": 4500, "currency": "usd", "paymentProcessor": "stripe", "invoiceId": null } } } ``` ### `partner.enrolled` This event is triggered when a [new partner is enrolled](/api-reference/endpoint/create-a-partner) in your partner program. The event payload contains the following: * `partner`: Details about the partner that was enrolled. * `links`: An array of the partner's referral links. Here's an example payload: ```json partner.enrolled { "id": "evt_ovabfqva8oqZzmLPN1JnwIfdt", "event": "partner.enrolled", "createdAt": "2025-04-08T17:11:56.492Z", "data": { "id": "pn_1JRB6678XHGBZE95R5PH5QVGS", "name": "Asleep Pink Mammal", "email": "chosen.blush.barracuda@dub-internal-test.com", "image": "https://api.dub.co/og/avatar?seed=Asleep Pink Mammal", "description": null, "country": "US", "payoutsEnabledAt": null, "paypalEmail": null, "stripeConnectId": null, "createdAt": "2025-04-08T17:11:56.446Z", "status": "approved", "programId": "prog_CYCu7IMAapjkRpTnr8F1azjN", "tenantId": null, "clicks": 0, "leads": 0, "sales": 0, "saleAmount": 0, "earnings": 0, "applicationId": null, "website": "https://example.com", "youtube": null, "twitter": null, "linkedin": null, "instagram": null, "tiktok": null, "links": [ { "id": "link_1JRB6677YXQB49RC1HKH7TPJE", "domain": "getacme.link", "key": "uvYO5pMIpctKdUVJlL3jIL4o", "shortLink": "https://getacme.link/uvYO5pMIpctKdUVJlL3jIL4o", "url": "https://acme.com", "clicks": 0, "leads": 0, "sales": 0, "saleAmount": 0 } ] } } ``` ## Link-level events Due to the high volume nature of these events, these events are scoped to a specific link. This means that you need to specify the link when creating a webhook – though you can select multiple links for the same webhook if you'd like. ### `link.clicked` This event is triggered when a user clicks on a link. The event payload contains all the details about the click event. Here's an example payload: ```json link.clicked { "id": "evt_b9ywgxWqai2glUpCQjclB17kM", "event": "link.clicked", "createdAt": "2024-08-30T10:16:13.149Z", "data": { "click": { "timestamp": "2024-08-30T10:16:12.124Z", "clickId": "d0UtZqE0BZuBPrJS", "url": "https://github.com/dubinc/dub", "ip": "63.141.57.109", "continent": "NA", "country": "US", "city": "San Francisco", "device": "Desktop", "browser": "Chrome", "os": "Mac OS", "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36", "bot": false, "qr": false, "referer": "(direct)" }, "link": { "id": "cm0faqkyn0001txvfwjfeq7gl", "domain": "dub.sh", "key": "79ys3WA", "externalId": null, "url": "https://github.com/dubinc/dub", "trackConversion": true, "archived": false, "expiresAt": null, "expiredUrl": null, "password": null, "proxy": false, "title": null, "description": null, "image": null, "video": null, "rewrite": false, "doIndex": false, "ios": null, "android": null, "geo": null, "publicStats": false, "comments": null, "shortLink": "https://dub.sh/79ys3WA", "qrCode": "https://api.dub.co/qr?url=https://dub.sh/79ys3WA?qr=1", "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_term": null, "utm_content": null, "userId": "cm022rkcw0000ikt14mscg9sg", "workspaceId": "ws_cm022sis60003ikt1syy7kfhl", "clicks": 11, "lastClicked": "2024-08-30T07:45:09.000Z", "leads": 6, "sales": 10, "saleAmount": 200000, "createdAt": "2024-08-29T13:03:59.098Z", "updatedAt": "2024-08-30T10:16:12.126Z" } } } ``` # Introduction Source: https://dub.co/docs/concepts/webhooks/introduction Use webhooks to get real-time notifications on events happening across your Dub workspace. Webhooks allows you to listen to real-time events happening across your Dub workspace. With webhooks, you can build custom integrations with Dub, such as: * Triggering a Zap on [Zapier](https://dub.co/integrations/zapier) when a new link is created in Dub * Sending click events in real-time to [Segment](https://dub.co/integrations/segment) for further processing * Get a [Slack](https://dub.co/integrations/slack) notification when someone clicks on your pitch deck link * Building gamified referral programs with [Dub Conversions](/conversions/quickstart) – e.g. increment usage credits for the referrer when a [new signup](/conversions/leads) happens The following endpoints do not trigger webhook events: [Bulk create links](/api-reference/endpoint/bulk-create-links), [Bulk update links](/api-reference/endpoint/bulk-update-links), [Bulk delete links](/api-reference/endpoint/bulk-delete-links). In this guide, we'll show you how to configure webhooks for your Dub workspace and a list of available events you can listen to. ## Creating a webhook To create a webhook for your Dub workspace, you'll need to follow these steps: Navigate to the [**Webhooks** settings page](https://app.dub.co/webhooks) in your Dub workspace. Create Webhook Click on **Create Webhook** to create a new webhook. Create Webhook Form Fill in the required fields in the webhook creation form: 1. **Name**: Give your webhook a name that helps you identify it. 2. **URL**: Enter the URL of the endpoint where you want to send the webhook. We recommend using [webhook.site](https://webhook.site/) to test your webhook. 3. **Signing secret**: This is an auto-generated secret key that you can use to verify the authenticity of the webhook in your application. Learn more about [verifying webhook requests](/concepts/webhooks/verify-webhook-requests). 4. **Events**: Select the events you want to listen to. You can select multiple events. Refer to the [Event Types](/concepts/webhooks/event-types) section to see the list of available events. Finally, click on **Create webhook** to create the webhook. ## Viewing webhook event logs We also provide you with a webhook event logs page where you can view all the webhook events that have been sent to your webhook endpoint in real-time. To view the webhook event logs, select the webhook from the [**Webhooks** settings page](https://app.dub.co/webhooks) and click on the **Webhook Logs** tab. Here, you'll see a list of all the webhook events that have been sent to your webhook endpoint: Webhook Event Logs You can also select on a specific event, which will open up a sheet with more details about the event: Webhook Event Logs Details ## Sending test events You can send test events to your webhook URL to ensure that it's working correctly. To do this: Navigate to the [**Webhooks** settings page](https://app.dub.co/webhooks) and select the webhook you want to test. Click on the **Update Details** tab to open the webhook details page. Select the `⋮` icon on the top right of the page, and click on **Send test event**. Send Test Event This will open up a modal where you can select the event you want to send. Send Test Event Modal Select the event you want to send, and click on **Send test webhook**. Send Test Webhook You'll see a success message and receive the webhook event in the webhook endpoint you specified. ## Retry Behaviour If your webhook endpoint does not respond with a success status code (2XX), we retry the request to ensure every message will be delivered. You can see all the retry attempts in your webhook event logs. Webhooks are retried until they are successfully delivered – with an exponential backoff to avoid overwhelming your webhook endpoint (also known as the "[thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem)"). The delay is capped at 24 hours from the 5th retry attempt onwards. | Retry attempt | Delay | | ------------- | -------- | | 1st | 12s | | 2nd | 2m 28s | | 3rd | 30m 8s | | 4th | 6h 7m 6s | | 5th | 24h | | 6th | 24h | | ... | ... | ### Temporary Disablement If a webhook endpoint consistently fails, it will be automatically disabled after a series of failed attempts. Notifications will be sent to the Workspace owners at the following intervals: * After 5, 10, and 15 consecutive failed attempts. * On the 20th consecutive failed attempt, the **webhook will be disabled**. This mechanism ensures that non-responsive endpoints do not continue to receive retry attempts indefinitely, maintaining system efficiency and preventing unnecessary load on both the sender and receiver. You can re-enable a disabled webhook by clicking on the **Enable webhook** button in the webhook details page. # Verify webhook requests Source: https://dub.co/docs/concepts/webhooks/verify-webhook-requests Learn how to verify webhook requests to ensure they're coming from Dub. With signature verification, you can determine if the webhook came from Dub, and has not been tampered with in transit. All webhooks are delivered with a `Dub-Signature` header. Dub generates this header using a secret key that only you and Dub know. An example header looks like this: ``` Dub-Signature: c9ed6a2abf93f59d761eea69908d8de00f4437b5b6d7cd8b9bf5719cbe61bf46 ``` ## Finding your webhook's signing secret You can find your webhook's signing secret in the **Update Details** tab: Webhook signing secret Make sure to keep this secret safe by only storing it in a secure environment variable (e.g. `DUB_WEBHOOK_SECRET`). Do not commit it to git or add it in any client-side code. ## Verifying a webhook request To verify, you can use the secret key to generate your own signature for each webhook. If both signatures match then you can be sure that a received event came from Dub. The steps required are: 1. Get the raw body of the request. 2. Extract the signature from the `Dub-Signature` header. 3. Calculate the HMAC of the raw body using the `SHA-256` hash function and the secret. 4. Compare the calculated `HMAC` with the one sent in the `Dub-Signature` header. If they match, the webhook is verified. Here's an example of how you can verify a webhook request in different languages: ```javascript Next.js export const POST = async (req: Request) => { const webhookSignature = req.headers.get("Dub-Signature"); if (!webhookSignature) { return new Response("No signature provided.", { status: 401 }); } // Copy this from the webhook details page const secret = process.env.DUB_WEBHOOK_SECRET; if (!secret) { return new Response("No secret provided.", { status: 401 }); } // Make sure to get the raw body from the request const rawBody = await req.text(); const computedSignature = crypto .createHmac("sha256", secret) .update(rawBody) .digest("hex"); if (webhookSignature !== computedSignature) { return new Response("Invalid signature", { status: 400 }); } // Handle the webhook event // ... }; ``` ```python Python import hmac import hashlib def webhook(): # Get the signature from the header webhook_signature = request.headers.get('Dub-Signature') if not webhook_signature: abort(401, 'No signature provided.') # Copy this from the webhook details page secret = os.environ.get('DUB_WEBHOOK_SECRET') if not secret: abort(401, 'No secret provided.') # Get the raw body of the request raw_body = request.data # Calculate the HMAC computed_signature = hmac.new( secret.encode('utf-8'), raw_body, hashlib.sha256 ).hexdigest() if webhook_signature != computed_signature: abort(400, 'Invalid signature') # Handle the webhook event # ... return 'OK', 200 ``` ```go Go import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "io/ioutil" "net/http" "os" ) func webhookHandler(w http.ResponseWriter, r *http.Request) { // Get the signature from the header webhookSignature := r.Header.Get("Dub-Signature") if webhookSignature == "" { http.Error(w, "No signature provided.", http.StatusUnauthorized) return } // Copy this from the webhook details page secret := os.Getenv("DUB_WEBHOOK_SECRET") if secret == "" { http.Error(w, "No secret provided.", http.StatusUnauthorized) return } // Read the raw body body, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "Error reading request body", http.StatusInternalServerError) return } // Calculate the HMAC h := hmac.New(sha256.New, []byte(secret)) h.Write(body) computedSignature := hex.EncodeToString(h.Sum(nil)) if webhookSignature != computedSignature { http.Error(w, "Invalid signature", http.StatusBadRequest) return } // Handle the webhook event // ... w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) } ``` ## Why is signature verification important? Signature verification is a crucial security measure that protects against request forgery and data tampering. Without verification, malicious actors could send fake webhook events to your endpoint, potentially triggering unauthorized actions. The HMAC-SHA256 signature verification process ensures that only Dub can generate valid webhook requests and that payloads haven't been modified in transit. This provides both authentication (confirming the sender is Dub) and integrity (ensuring the message hasn't been tampered with). # Appwrite Source: https://dub.co/docs/conversions/leads/appwrite Learn how to track lead conversion events with Appwrite and Dub Conversion tracking require a [Business plan](https://dub.co/pricing) subscription or higher. When it comes to [conversion tracking](/conversions/quickstart), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from: * Signing up for an account * Adding a product to cart * Joining a mailing list A diagram showing how lead events are tracked in the conversion funnel In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Appwrite for user authentication. ## Prerequisites Before you get started, make sure you follow the [Dub Conversions quickstart guide](/conversions/quickstart) to get Dub Conversions set up for your links: 1. [Enable conversion tracking for your links](/conversions/quickstart#step-1-enable-conversion-tracking-for-your-links) 2. [Install the @dub/analytics client-side SDK](/sdks/client-side/introduction) 3. [Install the Dub server-side SDK](/sdks/overview#server-side-sdks) ## Configure Appwrite Next, configure Appwrite to track lead conversion events during the sign up process. Head to [Appwrite Cloud](https://apwr.dev/appwrite-dub) and create a new project. New project on Appwrite Cloud Create a new API key with the `sessions.write` scope enabled and save the API key for later use. You can also copy your project ID and endpoint from the project's Settings page. API key in your project on Appwrite Cloud Then, in your Next.js app, install the Appwrite Node.js SDK. ```bash npm i node-appwrite ``` Add the following environment variables to your app. ```bash NEXT_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 NEXT_PUBLIC_APPWRITE_PROJECT= NEXT_APPWRITE_KEY= NEXT_DUB_API_KEY= ``` Add the `DubAnalytics` component from the `@dub/analytics` package to your app’s root layout. ```tsx src/app/layout.tsx import type { Metadata } from 'next'; import { Analytics as DubAnalytics } from '@dub/analytics/react'; export const metadata: Metadata = { title: 'Appwrite Dub Leads Example', description: 'Appwrite Dub Leads Tracking example app with Next.js' }; export default function RootLayout({ children }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } ``` Create the Appwrite Session and Admin client (necessary for SSR apps, as explained in the [Appwrite docs](https://appwrite.io/docs/products/auth/server-side-rendering)). Additionally, create a function to verify user login. ```ts src/lib/server/appwrite.ts 'use server'; import { Client, Account } from 'node-appwrite'; import { cookies } from 'next/headers'; export async function createSessionClient() { const client = new Client() .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT as string) .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT as string); const session = (await cookies()).get('my-custom-session'); if (!session || !session.value) { throw new Error('No session'); } client.setSession(session.value); return { get account() { return new Account(client); } }; } export async function createAdminClient() { const client = new Client() .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT as string) .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT as string) .setKey(process.env.NEXT_APPWRITE_KEY as string); return { get account() { return new Account(client); } }; } ``` Create the Dub client and send leads to Dub using the `dub.track.lead()` function. ```ts src/lib/server/dub.ts import type { Models } from 'node-appwrite'; import { Dub } from 'dub'; const dub = new Dub({ token: process.env.NEXT_DUB_API_KEY }); export function addDubLead(user: Models.User, dub_id: string) { dub.track.lead({ clickId: dub_id, eventName: 'Sign Up', externalId: user.$id, customerName: user.name, customerEmail: user.email }); } ``` In the `/auth` page, use the Appwrite Admin client to allow users to sign up. Post sign up, check if the `dub_id` cookie is present, send a lead event to Dub if found, and delete the `dub_id` cookie. ```tsx src/app/auth/page.tsx import { ID } from 'node-appwrite'; import { createAdminClient, getLoggedInUser } from '@/lib/server/appwrite'; import { cookies } from 'next/headers'; import { redirect } from 'next/navigation'; import { addDubLead } from '@/lib/server/dub'; async function signUpWithEmail(formData: any) { 'use server'; // Get sign up info from form const email = formData.get('email'); const password = formData.get('password'); const name = formData.get('name'); // Create account and session using Appwrite const { account } = await createAdminClient(); const user = await account.create(ID.unique(), email, password, name); const session = await account.createEmailPasswordSession(email, password); (await cookies()).set('my-custom-session', session.secret, { path: '/', httpOnly: true, sameSite: 'strict', secure: true }); // Check if Dub ID is present in cookies and track lead if found const dub_id = (await cookies()).get('dub_id')?.value; if (dub_id) { addDubLead(user, dub_id); (await cookies()).delete('dub_id'); } // Redirect to success page redirect('/auth/success'); } export default async function SignUpPage() { // Verify active user session and redirect to success page if found const user = await getLoggedInUser(); if (user) redirect('/auth/success'); return ( <>
); } ```
Here's the full list of attributes you can pass when sending a lead event: | Property | Required | Description | | :--------------- | :------- | :----------------------------------------------------------------------------------------------------------------------- | | `clickId` | **Yes** | The unique `dub_id` parameter that the lead conversion event is attributed to. | | `eventName` | **Yes** | The name of the event. Example: "Sign up". | | `externalId` | **Yes** | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer. | | `customerEmail` | No | The email address of the customer. If not passed, a random email address will be generated. | | `customerName` | No | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou"). | | `customerAvatar` | No | The avatar URL of the customer. If not passed, a random avatar URL will be generated. | ## Example App To learn more about how to track leads with Appwrite, check out the following example app: See how to track new user sign-ups with Appwrite and the Dub SDK. ## View your conversions Once you've enabled conversion tracking for your links, all your tracked conversions will show up on your [Analytics dashboard](https://app.dub.co/analytics). We provide 3 different views to help you understand your conversions: * **Time-series**: A [time-series view](https://dub.co/help/article/dub-analytics#1-time-series-analytics-chart) of the number clicks, leads and sales. Time-series line chart * **Funnel chart**: A funnel chart view visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales). Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales * **Real-time events stream**: A [real-time events stream](https://dub.co/help/article/real-time-events-stream) of every single conversion event that occurs across all your links in your workspace. The Events Stream dashboard on Dub # Auth0 Source: https://dub.co/docs/conversions/leads/auth0 Learn how to track lead conversion events with Auth0 and Dub Conversion tracking require a [Business plan](https://dub.co/pricing) subscription or higher. When it comes to [conversion tracking](/conversions/quickstart), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from: * Signing up for an account * Adding a product to cart * Joining a mailing list A diagram showing how lead events are tracked in the conversion funnel In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Auth0 for user authentication. ## Prerequisites Before you get started, make sure you follow the [Dub Conversions quickstart guide](/conversions/quickstart) to get Dub Conversions set up for your links: 1. [Enable conversion tracking for your links](/conversions/quickstart#step-1-enable-conversion-tracking-for-your-links) 2. [Install the @dub/analytics client-side SDK](/sdks/client-side/introduction) 3. [Install the Dub server-side SDK](/sdks/overview#server-side-sdks) ## Configure Auth0 Next, configure Auth0 to track lead conversion events. Here's how it works in a nutshell: 1. In the sign in `afterCallback` function, check if the user is a new sign up. 2. If the user is a new sign up, check if the `dub_id` cookie is present. 3. If the `dub_id` cookie is present, send a lead event to Dub using `dub.track.lead` 4. Delete the `dub_id` cookie. ```typescript app/api/auth/[auth0]/route.js import { handleAuth, handleCallback, type Session } from "@auth0/nextjs-auth0"; import { cookies } from "next/headers"; import { dub } from "@/lib/dub"; const afterCallback = async (req: Request, session: Session) => { const userExists = await getUser(session.user.email); if (!userExists) { createUser(session.user); // check if dub_id cookie is present const clickId = cookies().get("dub_id")?.value; if (clickId) { // send lead event to Dub await dub.track.lead({ clickId, eventName: "Sign Up", externalId: session.user.id, customerName: session.user.name, customerEmail: session.user.email, customerAvatar: session.user.image, }); // delete the dub_id cookie cookies().set("dub_id", "", { expires: new Date(0), }); } return session; } }; export default handleAuth({ callback: handleCallback({ afterCallback }), }); ``` Here's the full list of attributes you can pass when sending a lead event: | Property | Required | Description | | :--------------- | :------- | :----------------------------------------------------------------------------------------------------------------------- | | `clickId` | **Yes** | The unique `dub_id` parameter that the lead conversion event is attributed to. | | `eventName` | **Yes** | The name of the event. Example: "Sign up". | | `externalId` | **Yes** | The unique ID of the customer in your system. Will be used to identify and attribute all future events to this customer. | | `customerEmail` | No | The email address of the customer. If not passed, a random email address will be generated. | | `customerName` | No | The name of the customer. If not passed, a random name will be generated (e.g. "Big Red Caribou"). | | `customerAvatar` | No | The avatar URL of the customer. If not passed, a random avatar URL will be generated. | ## View your conversions Once you've enabled conversion tracking for your links, all your tracked conversions will show up on your [Analytics dashboard](https://app.dub.co/analytics). We provide 3 different views to help you understand your conversions: * **Time-series**: A [time-series view](https://dub.co/help/article/dub-analytics#1-time-series-analytics-chart) of the number clicks, leads and sales. Time-series line chart * **Funnel chart**: A funnel chart view visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales). Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales * **Real-time events stream**: A [real-time events stream](https://dub.co/help/article/real-time-events-stream) of every single conversion event that occurs across all your links in your workspace. The Events Stream dashboard on Dub # Better Auth Source: https://dub.co/docs/conversions/leads/better-auth Learn how to track lead conversion events with Better Auth and Dub Conversion tracking require a [Business plan](https://dub.co/pricing) subscription or higher. When it comes to [conversion tracking](/conversions/quickstart), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from: * Signing up for an account * Adding a product to cart * Joining a mailing list A diagram showing how lead events are tracked in the conversion funnel In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Better Auth for user authentication. ## Prerequisites Before you get started, make sure you follow the [Dub Conversions quickstart guide](/conversions/quickstart) to get Dub Conversions set up for your links: 1. [Enable conversion tracking for your links](/conversions/quickstart#step-1-enable-conversion-tracking-for-your-links) 2. [Install the @dub/analytics client-side SDK](/sdks/client-side/introduction) 3. [Install the Dub server-side SDK](/sdks/overview#server-side-sdks) ## Installation To get started, simply install the [`@dub/better-auth` plugin](https://www.npmjs.com/package/@dub/better-auth) via your preferred package manager: ```bash npm npm install @dub/better-auth ``` ```bash yarn yarn add @dub/better-auth ``` ```bash pnpm pnpm add @dub/better-auth ``` ```bash bun bun add @dub/better-auth ``` Then, add the plugin to your better-auth config file: ```ts auth.ts import { dubAnalytics } from "@dub/better-auth"; import { betterAuth } from "better-auth"; import { Dub } from "dub"; const dub = new Dub(); export const auth = betterAuth({ plugins: [ dubAnalytics({ dubClient: dub, }), ], }); ``` ## View your conversions Once you've enabled conversion tracking for your links, all your tracked conversions will show up on your [Analytics dashboard](https://app.dub.co/analytics). We provide 3 different views to help you understand your conversions: * **Time-series**: A [time-series view](https://dub.co/help/article/dub-analytics#1-time-series-analytics-chart) of the number clicks, leads and sales. Time-series line chart * **Funnel chart**: A funnel chart view visualizing the conversion & dropoff rates across the different steps in the conversion funnel (clicks → leads → sales). Funnel chart view showing the conversion & dropoff rates from clicks → leads → sales * **Real-time events stream**: A [real-time events stream](https://dub.co/help/article/real-time-events-stream) of every single conversion event that occurs across all your links in your workspace. The Events Stream dashboard on Dub # Clerk Source: https://dub.co/docs/conversions/leads/clerk Learn how to track lead conversion events with Clerk and Dub Conversion tracking require a [Business plan](https://dub.co/pricing) subscription or higher. When it comes to [conversion tracking](/conversions/quickstart), a `lead` event happens when a user performs an action that indicates interest in your product or service. This could be anything from: * Signing up for an account * Adding a product to cart * Joining a mailing list A diagram showing how lead events are tracked in the conversion funnel In this guide, we will be focusing on tracking new user sign-ups for a SaaS application that uses Clerk for user authentication. ## Prerequisites Before you get started, make sure you follow the [Dub Conversions quickstart guide](/conversions/quickstart) to get Dub Conversions set up for your links: 1. [Enable conversion tracking for your links](/conversions/quickstart#step-1-enable-conversion-tracking-for-your-links) 2. [Install the @dub/analytics client-side SDK](/sdks/client-side/introduction) 3. [Install the Dub server-side SDK](/sdks/overview#server-side-sdks) ## Configure Clerk Next, configure Clerk to track lead conversion events when a new user signs up. Here's a quick video showing how to do this: