# 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. # List all partners Source: https://dub.co/docs/api-reference/endpoint/retrieve-a-list-of-partners get /partners List all partners for a partner program. Partners endpoints require an [Advanced plan](https://dub.co/pricing) subscription or higher. # 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. ## 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 ``` # Deep link attribution Source: https://dub.co/docs/concepts/deep-links/attribution Learn how to use deep link attribution to track conversions events with Dub. This feature is coming soon. If you'd like early access, please [contact us](https://dub.co/contact/support). With [Dub Conversions](/conversions/quickstart), you can easily track conversion events from your deep links. # Deferred deep linking Source: https://dub.co/docs/concepts/deep-links/deferred-deep-linking Learn how to use deferred deep linking to track conversions and traffic. Deep links require a [Pro plan](https://dub.co/pricing) subscription or higher. Deferred deep linking allows you to track which link a user came from even when they don't have your app installed. Deferred deep linking on Dub When a user clicks a link without the app installed, they're redirected to the app store. After installing and opening the app, you can retrieve the original link information and redirect them to the appropriate screen. ## Android Play Store Android provides the [Install Referrer API](https://developer.android.com/google/play/installreferrer) which allows you to retrieve information about how a user came to install your app, including the referrer URL. Here is how it works in a nutshell: 1. User taps a deep link on a device without your app installed 2. Dub redirects the user to the App Store or Play Store by using [device targeting](https://dub.co/help/article/device-targeting) 3. User installs your app from the app store 4. App reads the install referrer on first launch 5. App extracts the deep link from the referrer URL and tracks the deep link open event using the [`/track/open` endpoint](/api-reference/endpoint/track-open) 6. Redirect the user to the appropriate screen using the destination URL returned by the `/track/open` endpoint ### Understanding the Referrer URL Structure When Dub redirects users to the Play Store, the referrer URL contains the deep link information in a nested structure. The referrer URL looks like this: ``` https://play.google.com/store/apps/details?id=com.example.app&referrer=deepLink%3Dhttps%253A%252F%252Fdub.sh%252Fgps ``` To extract the original deep link, you need to: 1. Parse the `referrer` parameter from the Play Store URL 2. URL decode it to get the `deepLink` parameter 3. URL decode the `deepLink` value to get the actual deep link The code examples below show how to implement this extraction process. ### Step 1: Add the Install Referrer dependency First, you'll need to add the Google Play Install Referrer library to your project. ```javascript React Native // package.json { "dependencies": { "react-native-play-install-referrer": "latest" } } ``` ```kotlin Android // app/build.gradle dependencies { implementation 'com.android.installreferrer:installreferrer:2.2' } ``` ### Step 2: Implement the Install Referrer logic Now you'll need to implement the logic to read the install referrer, extract the deep link, and track the deep link open. ```javascript React Native // InstallReferrerTracker.js import { PlayInstallReferrer } from "react-native-play-install-referrer"; class InstallReferrerTracker { constructor() { this.isFirstLaunch = true; } trackInstallReferrer() { // Check if this is the first launch if (!this.isFirstLaunch) { return; } PlayInstallReferrer.getInstallReferrerInfo((installReferrerInfo, error) => { if (!error) { console.log( "Install referrer = " + installReferrerInfo.installReferrer ); if (installReferrerInfo.installReferrer) { // Extract the deep link from the referrer URL const deepLink = this.extractDeepLinkFromReferrer( installReferrerInfo.installReferrer ); if (deepLink) { // Track the deep link open with the extracted URL this.trackDeepLinkOpen(deepLink); } } } else { console.log("Failed to get install referrer info!"); console.log("Response code: " + error.responseCode); console.log("Message: " + error.message); } this.isFirstLaunch = false; }); } extractDeepLinkFromReferrer(referrerUrl) { try { // Parse the referrer URL to extract the deep link // e.g. for referrer=deepLink%3Dhttps%253A%252F%252Fdub.sh%252Fgps // the deep link is https://dub.sh/gps const referrerUrlObj = new URL(referrerUrl); const deepLinkParam = referrerUrlObj.searchParams.get("deepLink"); return decodeURIComponent(deepLinkParam); return null; } catch (error) { console.error("Error extracting deep link from referrer:", error); return null; } } async trackDeepLinkOpen(deepLink) { try { const response = await fetch("https://api.dub.co/track/open", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ deepLink, }), }); if (response.ok) { const data = await response.json(); const destinationUrl = data.link.url; // Navigate to the destination URL in your app this.navigateToDestination(destinationUrl); } } catch (error) { console.error("Error tracking deep link open:", error); } } navigateToDestination(destinationUrl) { // Implement your navigation logic here // This will depend on your navigation library (React Navigation, etc.) console.log("Navigating to:", destinationUrl); } } export default InstallReferrerTracker; ``` ```kotlin Android // InstallReferrerTracker.kt import android.content.Context import com.android.installreferrer.api.InstallReferrerClient import com.android.installreferrer.api.InstallReferrerResponse import com.android.installreferrer.api.ReferrerDetails import com.android.installreferrer.api.InstallReferrerStateListener import org.json.JSONObject import java.net.HttpURLConnection import java.net.URL class InstallReferrerTracker(private val context: Context) { private var isFirstLaunch = true fun trackInstallReferrer() { if (!isFirstLaunch) { return } val referrerClient = InstallReferrerClient.newBuilder(context).build() referrerClient.startConnection(object : InstallReferrerStateListener { override fun onInstallReferrerSetupFinished(responseCode: Int) { when (responseCode) { InstallReferrerResponse.OK -> { val response: ReferrerDetails = referrerClient.installReferrer val referrerUrl = response.installReferrer if (!referrerUrl.isNullOrEmpty()) { // Extract the deep link from the referrer URL val deepLink = extractDeepLinkFromReferrer(referrerUrl) if (!deepLink.isNullOrEmpty()) { // Track the deep link open with the extracted URL trackDeepLinkOpen(deepLink) } } } InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> { // API not available on the current Play Store app println("Install Referrer API not supported") } InstallReferrerResponse.SERVICE_UNAVAILABLE -> { // Connection couldn't be established println("Install Referrer service unavailable") } } referrerClient.endConnection() } override fun onInstallReferrerServiceDisconnected() { // Try to restart the connection on the next request } }) } private fun extractDeepLinkFromReferrer(referrerUrl: String): String? { return try { // e.g. for referrer=deepLink%3Dhttps%253A%252F%252Fdub.sh%252Fgps // the deep link is https://dub.sh/gps val referrerUrlObj = java.net.URL(referrerUrl) val query = referrerUrlObj.query ?: return null // Parse query parameters val params = query.split("&").associate { param -> val keyValue = param.split("=", limit = 2) if (keyValue.size == 2) { keyValue[0] to keyValue[1] } else { keyValue[0] to "" } } val deepLinkParam = params["deepLink"] ?: return null // URL decode the deepLinkParam value to get the actual deep link java.net.URLDecoder.decode(deepLinkParam, "UTF-8") } catch (e: Exception) { println("Error extracting deep link from referrer: ${e.message}") null } } private fun trackDeepLinkOpen(deepLink: String) { Thread { try { val url = URL("https://api.dub.co/track/open") val connection = url.openConnection() as HttpURLConnection connection.requestMethod = "POST" connection.setRequestProperty("Content-Type", "application/json") connection.doOutput = true val body = JSONObject().apply { put("deepLink", deepLink) } connection.outputStream.use { os -> os.write(body.toString().toByteArray()) } val responseCode = connection.responseCode if (responseCode == HttpURLConnection.HTTP_OK) { val response = connection.inputStream.bufferedReader().use { it.readText() } val jsonResponse = JSONObject(response) val link = jsonResponse.getJSONObject("link") val destinationUrl = link.getString("url") // Navigate to the destination URL on the main thread navigateToDestination(destinationUrl) } } catch (e: Exception) { println("Error tracking deep link open: ${e.message}") } }.start() } private fun navigateToDestination(destinationUrl: String) { // Implement your navigation logic here // This will depend on your navigation library (Navigation Component, etc.) println("Navigating to: $destinationUrl") } } ``` ### Step 3: Initialize the tracker in your app Now you need to initialize the Install Referrer tracker when your app starts. ```javascript React Native // App.js import React, { useEffect } from 'react'; import InstallReferrerTracker from './InstallReferrerTracker'; const App = () => { useEffect(() => { const tracker = new InstallReferrerTracker(); // Track install referrer on app launch tracker.trackInstallReferrer(); }, []); return ( // Your app components ); }; export default App; ``` ```kotlin Android // MainActivity.kt import android.os.Bundle import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Initialize and track install referrer val tracker = InstallReferrerTracker(this) tracker.trackInstallReferrer() } } ``` ### Step 4: Handle the navigation Finally, implement the navigation logic to redirect users to the appropriate screen based on the destination URL. ```javascript React Native // InstallReferrerTracker.js (navigation method) navigateToDestination(destinationUrl) { // Parse the destination URL to determine which screen to navigate to const url = new URL(destinationUrl); const path = url.pathname; // Example navigation logic if (path.includes('/product/')) { const productId = path.split('/product/')[1]; // Navigate to product detail screen navigation.navigate('ProductDetail', { productId }); } else if (path.includes('/category/')) { const categoryId = path.split('/category/')[1]; // Navigate to category screen navigation.navigate('Category', { categoryId }); } else { // Navigate to home screen navigation.navigate('Home'); } } ``` ```kotlin Android // InstallReferrerTracker.kt (navigation method) import android.content.Intent import android.net.Uri import android.os.Handler import android.os.Looper private fun navigateToDestination(destinationUrl: String) { Handler(Looper.getMainLooper()).post { try { val uri = Uri.parse(destinationUrl) val path = uri.path ?: "" // Example navigation logic when { path.contains("/product/") -> { val productId = path.split("/product/").lastOrNull() // Navigate to product detail screen val intent = Intent(context, ProductDetailActivity::class.java).apply { putExtra("productId", productId) } context.startActivity(intent) } path.contains("/category/") -> { val categoryId = path.split("/category/").lastOrNull() // Navigate to category screen val intent = Intent(context, CategoryActivity::class.java).apply { putExtra("categoryId", categoryId) } context.startActivity(intent) } else -> { // Navigate to home screen val intent = Intent(context, HomeActivity::class.java) context.startActivity(intent) } } } catch (e: Exception) { println("Error navigating to destination: ${e.message}") } } } ``` ### Important notes 1. **First Launch Detection**: The install referrer is only available on the first launch after installation. Make sure to track this properly to avoid duplicate tracking. 2. **URL Decoding**: The referrer URL is URL-encoded multiple times. Make sure to properly decode it to extract the original deep link. 3. **Network Operations**: Ensure that all API calls are made in background threads to avoid blocking the main thread and ensure smooth app performance. 4. **Error Handling**: Always implement proper error handling for network requests and URL parsing. 5. **Testing**: Test your implementation thoroughly using the Google Play Console's internal testing track. To get started, we recommend using the [quickstart guide](/concepts/deep-links/quickstart) to set up your deep links on Dub. ### Related resources * [Google Play Install Referrer API Documentation](https://developer.android.com/google/play/installreferrer) * [Android Deep Linking Guide](https://developer.android.com/training/app-links) * [Dub API Documentation](/api-reference/introduction) ## iOS App Store Unlike Android, iOS doesn't provide a built-in install referrer API. Dub implements a hybrid approach combining **deterministic tracking** (clipboard-based) and **probabilistic tracking** (IP-based) to ensure reliable deferred deep linking on iOS. ### How iOS deferred deep linking works on Dub Dub's iOS deferred deep linking solution provides two tracking approaches: 1. **Deterministic clipboard-based tracking**: Uses iOS clipboard data to reliably identify and open the exact deep link the user interacted with. 2. **Probabilistic IP-based tracking**: Matches users from the web to the app based on their IP address. For the deterministic approach, when an iOS user who doesn't have your app installed clicks on your deep link, Dub redirects them to a custom landing page: iOS deferred deep linking landing page This page displays two options: * **Get the App**: Copies the deep link to the clipboard before redirecting to the App Store. * **Get the App without Copying**: Redirects directly to the App Store without copying the deep link to the clipboard. After the user installs your app, the deep link resolution method depends on the button they clicked. #### 1. Deterministic clipboard-based tracking This method uses the iOS clipboard to pass the deep link into your app after installation, ensuring reliable and direct link resolution. Here is how it works in a nutshell: 1. User taps **Get the App** button on the landing page. 2. Dub copies the deep link URL to the clipboard. 3. The user is redirected to the App Store to download your app. 4. After installation, your app reads the clipboard to retrieve the deep link. 5. Your app calls the [`/track/open` endpoint](/api-reference/endpoint/track-open) with the `deepLink` parameter in the request body. 6. Dub returns the destination URL, and your app navigates the user to the appropriate screen (see [deep links quickstart](/concepts/deep-links/quickstart) for more details). #### 2. Probabilistic IP-based tracking This method relies on matching the user’s device IP address from the initial click event to the app open event after installation. 1. User taps **Get the App without Copying** button on the landing page. 2. The user is redirected directly to the App Store to download your app. 3. Dub has already tracked the click and stored the IP address at the time of deep link click. 4. After installation, your app calls the [`/track/open` endpoint](/api-reference/endpoint/track-open) with the `dubDomain` parameter in the request body. 5. Dub matches the user using IP-based tracking and returns the destination URL. 6. If a destination URL is returned, your app navigates the user to the appropriate screen (see [deep links quickstart](/concepts/deep-links/quickstart) for more details). ### Track the deep link open The following logic determines whether to use clipboard-based tracking or IP-based tracking: * **Clipboard-based tracking**: Used when a deep link is found in the clipboard (e.g., `acme.link`). The app sends the `deepLink` parameter in the request body. * **IP-based tracking**: Used when no deep link is found in the clipboard. The app sends only the `dubDomain` parameter, allowing Dub to match the user based on their IP address. This ensures the most reliable tracking method is chosen automatically. ```javascript React Native // App.js import React, { useEffect } from 'react'; import Clipboard from '@react-native-clipboard/clipboard'; import AsyncStorage from '@react-native-async-storage/async-storage'; export default function App() { useEffect(() => { trackOpen(); }, []); return ( // Your app components ); } // Make request to /track/open endpoint async function trackOpen() { try { // Check if this is first launch const hasLaunched = await AsyncStorage.getItem('app_first_launch'); if (hasLaunched !== null) { return; } await AsyncStorage.setItem('app_first_launch', 'false'); // Check clipboard for deep link const clipboard = await Clipboard.getString(); let requestBody; if (clipboard && clipboard.includes('acme.link')) { // Clipboard-based tracking requestBody = { deepLink: clipboard }; } else { // IP-based tracking fallback requestBody = { dubDomain: 'acme.link' }; } const response = await fetch('https://api.dub.co/track/open', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody), }); if (response.ok) { const data = await response.json(); const destinationURL = data.link?.url; if (destinationURL) { // Navigate to the destination URL console.log('Navigating to:', destinationURL); } } } catch (error) { console.error('Error tracking open:', error); } } ``` ```swift iOS import SwiftUI @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView().onAppear { trackOpen() } } } } // Make request to /track/open endpoint func trackOpen(completion: @escaping (String?) -> Void) { let clipboard = UIPasteboard.general.string var requestBody: [String: String] if let deepLink = clipboard, deepLink.contains("acme.link") { // Clipboard-based tracking requestBody = ["deepLink": deepLink] } else { // IP-based tracking fallback requestBody = ["dubDomain": "acme.link"] } guard let url = URL(string: "https://api.dub.co/track/open") else { completion(nil) return } var request = URLRequest(url: url) request.httpMethod = "POST" request.addValue("application/json", forHTTPHeaderField: "Content-Type") do { request.httpBody = try JSONSerialization.data(withJSONObject: requestBody) } catch { print("Error creating request body:", error) completion(nil) return } URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { print("Error tracking open:", error) completion(nil) return } guard let data = data else { print("No data received") completion(nil) return } do { let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] let link = json?["link"] as? [String: Any] let destinationURL = link?["url"] as? String completion(destinationURL) } catch { print("Error parsing response:", error) completion(nil) } }.resume() } ``` ### Handle the navigation Once you have the deep link URL from your `trackOpen` function, you can route the user to the appropriate screen. ```javascript React Native // App.js with React Navigation import React, { useEffect, useRef } from "react"; import { NavigationContainer } from "@react-navigation/native"; import { createNativeStackNavigator } from "@react-navigation/native-stack"; const Stack = createNativeStackNavigator(); export default function App() { const navigationRef = useRef(); useEffect(() => { const handleDeepLink = async () => { const destinationURL = await trackOpen(); if (destinationURL && navigationRef.current) { // Parse URL and navigate const url = new URL(destinationURL); const path = url.pathname; if (path.includes("/product/")) { const productId = path.split("/product/")[1]; navigationRef.current.navigate("Product", { productId }); } else { navigationRef.current.navigate("Home"); } } }; handleDeepLink(); }, []); return ( ); } ``` ```swift iOS import SwiftUI struct ContentView: View { @State private var deepLinkURL: String? @State private var shouldNavigate = false var body: some View { NavigationStack { VStack { Text("Home Screen") } .onAppear { trackOpen { url in if let url = url { DispatchQueue.main.async { deepLinkURL = url shouldNavigate = true } } } } .navigationDestination(isPresented: $shouldNavigate) { if let url = deepLinkURL { DetailView(url: url) } } } } } struct DetailView: View { let url: String var body: some View { Text("Navigated to: \(url)") } } ``` ### Important notes 1. **IP-based Tracking Limitations**: IP-based tracking relies on the device’s network IP, which can be less reliable in environments like corporate networks, VPNs, or shared Wi-Fi. 2. **Network Operations**: Ensure that all API calls are made in background threads to avoid blocking the main thread and ensure smooth app performance. 3. **Error Handling**: Implement proper error handling for network requests, clipboard access to ensure the app behaves gracefully under failures. 4. **Testing**: Test thoroughly using TestFlight or an internal distribution build to confirm the flow works for both clipboard-based and IP-based tracking scenarios. 5. **App Store Guidelines**: Ensure your app complies with Apple's App Store guidelines regarding clipboard access and user privacy. ### Related resources * [iOS Deep Linking Guide](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content) * [`/track/open` API endpoint reference](/api-reference/endpoint/track-open) * [Dub Deep Links Quickstart](/concepts/deep-links/quickstart) # Migrating from Firebase Dynamic Links Source: https://dub.co/docs/concepts/deep-links/migrating-from-firebase Learn how to migrate from Firebase Dynamic Links to Dub. Deep links require a [Pro plan](https://dub.co/pricing) subscription or higher. This guide walks you through replacing [Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links) with Dub for deep link creation, click tracking, and routing across platforms. ## Prerequisites Before you begin, make sure to follow the [quickstart guide](/concepts/deep-links/quickstart) to set up deep links on Dub by completing the following steps: 1. Configure your [deep link domain](/concepts/deep-links/quickstart#step-1%3A-configure-your-deep-link-domains-on-dub) on Dub. This replaces Firebase's `*.page.link` domain with your own branded deep link domain. 2. [Create your deep links](/concepts/deep-links/quickstart#step-2%3A-create-your-deep-links-on-dub) on Dub. 3. [Handle deep link redirects inside your app](/concepts/deep-links/quickstart#step-3%3A-handling-deep-link-redirects-in-your-app) (more details below). ## Migrating Android apps If you have the Firebase Dynamic Links SDK installed, remove it from your `build.gradle` file: ```gradle implementation 'com.google.firebase:firebase-dynamic-links:21.1.0' ``` Remove any Firebase imports from your Kotlin/Java files: ```java import com.google.firebase.dynamiclinks.FirebaseDynamicLinks import com.google.firebase.dynamiclinks.DynamicLink ``` If you’re using the same domain for both Firebase Dynamic Links and Dub (e.g., yourapp.com), no changes are needed — you can skip this section. However, if you were previously using Firebase’s branded domain (e.g., yourapp.page.link), you’ll need to replace it with your custom Dub domain in your app’s configuration. ```xml ``` Override `onNewIntent` in your main activity to handle deep link opens: ```kotlin override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) intent?.data?.let { uri -> handleDeepLink(uri) } } private fun handleDeepLink(uri: Uri) { // Track the deep link open and get destination URL trackDeepLinkClick(uri.toString()) } private fun trackDeepLinkClick(deepLink: String) { val url = URL("https://api.dub.co/track/open") val connection = url.openConnection() as HttpURLConnection connection.requestMethod = "POST" connection.setRequestProperty("Content-Type", "application/json") connection.doOutput = true val body = JSONObject().apply { put("deepLink", deepLink) } connection.outputStream.use { os -> os.write(body.toString().toByteArray()) } val response = connection.inputStream.bufferedReader().use { it.readText() } val jsonResponse = JSONObject(response) val destinationUrl = jsonResponse.getString("url") runOnUiThread { navigateToDestination(destinationUrl) } } ``` In a nutshell, this code: * Extracts the domain and key from the deep link URL * Sends the data to the [`/track/open` endpoint](/api-reference/endpoint/track-open) * Retrieves the final destination URL * Navigates to the destination URL in the app ## Migrating iOS apps If you have the Firebase Dynamic Links SDK installed, remove it from your `Podfile`: ```ruby pod 'Firebase/DynamicLinks' ``` Remove any Firebase imports like: ```swift import FirebaseDynamicLinks ``` If you're using the same domain for both Firebase Dynamic Links and Dub (e.g., yourapp.com), no changes are needed — you can skip this section. However, if you were using Firebase’s branded domain (e.g., yourapp.page.link), you’ll need to replace it with your Dub domain in your iOS project setup. To enable Universal Links with Dub, update your Associated Domains in Xcode: 1. Open your Xcode project. 2. Select your app target → **Signing & Capabilities** 3. Under **Associated Domains**, replace the old Firebase domain with your Dub domain: No changes to your app’s `Info.plist` are required unless you had other Firebase-specific deep link configurations. ```swift import Foundation import UIKit func handleDubDeepLink(\_ url: URL) { let domain = url.host ?? "" let key = url.lastPathComponent let payload: [String: Any] = [ "domain": domain, "key": key, ] guard let requestURL = URL(string: "https://api.dub.co/track/open"), let httpBody = try? JSONSerialization.data(withJSONObject: payload) else { print("Invalid request setup") return } var request = URLRequest(url: requestURL) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = httpBody // Send the request URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { print("Failed to track deep link: \(error)") return } guard let data = data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let destination = json["url"] as? String else { print("Invalid response from /track/open") return } print("Resolved destination: \(destination)") DispatchQueue.main.async { // Navigate to the destination URL or handle it in-app navigateToScreen(from: destination) } }.resume() } func navigateToScreen(from url: String) { print("Navigating to in-app destination: \(url)") // Add your navigation logic here } ``` In a nutshell, this code: * Sends the deep link URL to the [`/track/open` endpoint](/api-reference/endpoint/track-open) * Retrieves the final destination URL * Navigates to the destination URL in the app # Quickstart Source: https://dub.co/docs/concepts/deep-links/quickstart Learn how to use Dub to create deep links for your mobile app (with native support for React Native, iOS, and Android). Deep links require a [Pro plan](https://dub.co/pricing) subscription or higher. On Dub, you can create deep links that lets you to redirect users to a specific page within your mobile application. Deep links on Dub For example, you're creating an ad campaign to drive traffic to a specific product page within your mobile app. By following the quickstart guide below, you'll be able to make sure that all your short links are set up with deep linking functionality. ## Step 1: Configure your deep link domains on Dub Before you can create deep links, you need to configure your deep link domains in your Dub workspace. This involves adding a custom domain that will be used for your deep links and configuring your deep link configuration files. First, you'll need to add a custom domain to your Dub workspace. Navigate to your [workspace domain settings](https://app.dub.co/settings/domains) and click **Add Domain**. You can use a domain you already own, or leverage our [free .link domain offer](https://dub.co/help/article/free-dot-link-domain) to register a custom domain like `yourcompany.link` and use it as your deep link domain. Once you've set up your custom domain, you'll need to upload your deep link configuration files to Dub, which we'll host under the `/.well-known/` directory of your domain. To do that, go to your [workspace domain settings](https://app.dub.co/settings/domains) and click on the edit button for your custom domain: Deep link configuration files In the domain modal, click on **Show advanced settings**, which will open up the Deep Link Configuration settings fields. Deep link configuration settings **iOS (apple-app-site-association)** For iOS apps, upload your Apple App Site Association file to enable iOS deep links: ```json apple-app-site-association { "applinks": { "apps": [], "details": [ { "appID": "YOUR_APP_ID", "paths": [] } ] } } ``` **Android (assetlinks.json)** For Android apps, upload your AssetLinks file to enable Android deep links: ```json assetlinks.json [ { "target": { "namespace": "android_app", "package_name": "YOUR_PACKAGE_NAME", "sha256_cert_fingerprints": [] }, "relation": ["delegate_permission/common.handle_all_urls"] } ] ``` Once you've set up your deep link configuration files, you can go to their respective URLs to verify that they've been configured correctly: **iOS:** `yourdomain.link/.well-known/apple-app-site-association` [(example)](https://getacme.link/.well-known/apple-app-site-association) **Android:** `yourdomain.link/.well-known/assetlinks.json` [(example)](https://getacme.link/.well-known/assetlinks.json) Last but not least, you'll need to whitelist your deep link domain in your apps to allow them to redirect straight into a page within your app. **For iOS apps**, you'll need to [allow websites to link to your apps](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/). **For Android apps**, you'll need to [verify your Android app links](https://developer.android.com/training/app-links/verify-android-applinks). ## Step 2: Create your deep links on Dub Now that you've configured your deep link domains, you can create deep links that will redirect users to specific content within your app. Go to your [Dub dashboard](https://app.dub.co) and click **Create Link** in the top navigation bar. Enter your destination URL in the "Destination URL" field. You can enter the URL with or without the `https://` protocol – behind the scenes, Dub will automatically make sure it's formatted correctly. This is the URL that opens a specific screen or piece of content within your app. For example `https://yourapp.com/product/Apple-MacBook` opens the product detail screen for `Apple-MacBook`. Deep link builder Under the Short Link field, you can either: 1. Enter your own short link slug 2. Generate a random short link slug 3. Generate a short link slug with AI. For example, you can set a short link slug like `apple-macbook` to open the product detail screen for `Apple-MacBook`. This step is optional. If you don't enter a short link slug, Dub will generate a random 7-character slug for you. [Device Targeting](https://dub.co/help/article/device-targeting) enables you to redirect users to the App Store or Google Play Store if your app isn’t installed. For example, you can set custom destination URLs for different devices using the link builder — use the **iOS Targeting** input for iOS devices, and the **Android Targeting** input for Android devices. Deep link device targeting configuration Finally, click **Create link** to create your deep link. This link will act as a Firebase Dynamic Link replacement. Dub's link builder offers many additional features like UTM builder, password protection, expiration dates, geo targeting, and more. [Learn more about creating links on Dub](https://dub.co/help/article/how-to-create-link) to explore all available options. ## Step 3: Handling deep link redirects in your app When a user opens your app from a deep link, you need to handle two main scenarios: ### Scenario 1: User has your app installed When your app is already installed, the deep link will open your app directly. Here's how to handle it: Your app will receive the deep link URL when it opens. The URL will be in the format: `https://yourdomain.link/short-link-slug` ```javascript React Native // App.js import { useEffect } from "react"; import { Linking } from "react-native"; useEffect(() => { const handleDeepLink = (url) => { try { // Call the tracking endpoint with the full deep link URL trackDeepLinkClick(url); } catch (error) { console.error("Error handling deep link URL:", error); } }; // Handle deep link when app is already running const subscription = Linking.addEventListener("url", (event) => { handleDeepLink(event.url); }); // Handle deep link when app is opened from a deep link Linking.getInitialURL().then((url) => { if (url) { handleDeepLink(url); } }); return () => { subscription?.remove(); }; }, []); ``` ```swift iOS // AppDelegate.swift func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { handleDeepLink(url: url) return true } func handleDeepLink(url: URL) { // Call the tracking endpoint with the full deep link URL trackDeepLinkClick(deepLink: url.absoluteString) } ``` ```kotlin Android // MainActivity.kt override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) intent?.data?.let { uri -> handleDeepLink(uri) } } private fun handleDeepLink(uri: Uri) { // Call the tracking endpoint with the full deep link URL trackDeepLinkClick(uri.toString()) } ``` Once you’ve detected and parsed the deep link, you should track the open event using Dub's API, which will return the final destination URL in the API response. To do this, make a `POST` request to the [`/track/open` endpoint](/api-reference/endpoint/track-open) with the following body: ```json { "deepLink": "https://yourdomain.link/short-link-slug" } ``` The response will be a JSON object with the following structure if the request is successful: ```json { "clickId": "ASltDhoxBiBqKH00", "link": { "id": "link_1K9XQTLF7OSH3Z48DKQ5WSRVY", "domain": "yourdomain.link", "key": "short-link-slug", "url": "https://yourapp.com/product/Apple-MacBook" } } ``` Now you've got the destination URL (via `link.url`), you can navigate the user to the correct screen in your app. Here's the full example code for React Native, iOS, and Android. ```javascript React Native // DeepLinkTracker.js async function trackDeepLinkClick(deepLink) { try { const response = await fetch(`https://api.dub.co/track/open`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ deepLink, }), }); if (response.ok) { const data = await response.json(); const destinationUrl = data.link.url; // Navigate to the destination URL in your app navigateToDestination(destinationUrl); } } catch (error) { console.error("Error tracking deep link:", error); } } ``` ```swift iOS // DeepLinkTracker.swift func trackDeepLinkClick(deepLink: String) { let url = URL(string: "https://api.dub.co/track/open")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body = [ "deepLink": deepLink, ] request.httpBody = try? JSONSerialization.data(withJSONObject: body) URLSession.shared.dataTask(with: request) { data, response, error in if let data = data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let link = json["link"] as? [String: Any], let destinationUrl = link["url"] as? String { DispatchQueue.main.async { self.navigateToDestination(destinationUrl) } } }.resume() } ``` ```kotlin Android // DeepLinkTracker.kt private fun trackDeepLinkClick(deepLink: String) { val url = URL("https://api.dub.co/track/open") val connection = url.openConnection() as HttpURLConnection connection.requestMethod = "POST" connection.setRequestProperty("Content-Type", "application/json") connection.doOutput = true val body = JSONObject().apply { put("deepLink", deepLink) } connection.outputStream.use { os -> os.write(body.toString().toByteArray()) } val response = connection.inputStream.bufferedReader().use { it.readText() } val jsonResponse = JSONObject(response) val link = jsonResponse.getJSONObject("link") val destinationUrl = link.getString("url") runOnUiThread { navigateToDestination(destinationUrl) } } ``` ### Scenario 2: User doesn't have your app installed When your app isn't installed, the user will be redirected to the App Store or Google Play Store since you've enabled [device targeting](https://dub.co/help/article/device-targeting) in Step 2 above. After they install and open your app, you'll need to use [deferred deep linking](/concepts/deep-links/deferred-deep-linking) to: 1. Retrieve the short link that brought the user to the app store 2. Track the open event via the [`/track/open` endpoint](/api-reference/endpoint/track-open) 3. Redirect the user to the final destination URL For detailed implementation of deferred deep linking, including how to use the Google Play Store Install Referrer API and other services, see our [Deferred Deep Linking](/concepts/deep-links/deferred-deep-linking) guide. ## Test your deep links Before deploying your deep links to production, test them thoroughly using emulators to ensure they work correctly. Use the Android Debug Bridge (adb) to test your deep links on an Android emulator: ```bash adb shell am start -W -a android.intent.action.VIEW -d "https://yourdomain.link/your-short-link" com.yourpackage.name ``` Use the `xcrun` command to test your deep links on an iOS simulator: ```bash xcrun simctl openurl booted "https://yourdomain.link/your-short-link" ``` You can also test your deep links by opening them from a messaging app on your device: 1. Open a messaging app (like Messages, WhatsApp, or Telegram) on your device 2. Send yourself a message containing your deep link URL 3. Tap on the link in the message 4. The link should either open your app directly or redirect to the appropriate app store # 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 | | [Folders](#folders) | One-to-many | The ID of the folder that contains the link. | Organizing links into hierarchical folder structures for better organization. | ## 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' ``` *** ## Folders Folders are a hierarchical way to organize your links on Dub. With folders, you can: * Organize your links into directories * [Filter your links by folder](#retrieve-links-by-folders) and get a shareable link to the filtered results. * [Filter your analytics by folder](#retrieve-analytics-by-folders) to see how your campaigns are performing. ### Create link with folderId You can use the folder ID to create a link with a folder. ```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", folderId: "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", FolderId: "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", "folder_id": "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", folder_id: "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', folderId: '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", "folderId": "clux0rgak00011..." }' ``` ### Retrieve links by folderId Here is how to retrieve links by folder 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({ folderId: "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.List(ctx, operations.GetLinksRequest{ FolderId: dubgo.String("clux0rgak00011..."), }) } ``` ```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={ "folder_id": "clux0rgak00011...", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::GetLinksRequest.new( folder_id: "clux0rgak00011...", ) 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( folderId: "clux0rgak00011...", ); $responses = $sdk->links->list( request: $request ); ``` ```bash cURL curl --request GET \ --url https://api.dub.co/links?folderId=clux0rgak00011... \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` ### Retrieve analytics by folderId You can retrieve analytics by folderId by passing the `folderId` prop. This is helpful for fetching the analytics for all the links under a specific folder, or the total analytics for a folder. ```javascript Node.js import { Dub } from "dub"; export const dub = new Dub({ token: process.env.DUB_API_KEY, }); const analytics = await dub.analytics.retrieve({ folderId: "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.Analytics.Retrieve(ctx, operations.RetrieveAnalyticsRequest{ FolderId: dubgo.String("clux0rgak00011..."), }) } ``` ```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={ "folder_id": "clux0rgak00011...", }) ``` ```ruby Ruby require 'dub' s = ::OpenApiSDK::Dub.new s.config_security( ::OpenApiSDK::Shared::Security.new( token: "DUB_API_KEY", ) ) req = ::OpenApiSDK::Operations::RetrieveAnalyticsRequest.new( folder_id: "clux0rgak00011...", ) 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( folderId: "clux0rgak00011...", ); $response = $sdk->analytics->retrieve( request: $request ); ``` ```bash cURL curl --request GET \ --url https://api.dub.co/analytics?folderId=clux0rgak00011... \ --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) * [`commission.created`](#commission-created) ### `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 } ] } } ``` ### `commission.created` This event is triggered whenever a new commission is generated in your partner program — either automatically through a tracked conversion or manually via your dashboard. The event payload contains the following: * `partner`: Details about the partner that earned the commission. * `customer`: Details about the customer that made the purchase. * `userId`: The ID of the user who created the manual commission. You can use this field to distinguish manual commissions from other types of commissions. Here's an example payload: ```json commission.created { "id": "evt_64dv6vxYVgltzJBKc9ujJ1ghL", "event": "commission.created", "createdAt": "2025-07-16T10:48:15.468Z", "data": { "id": "cm_1K09DJTBCRT24P6BRD515CK29", "type": "sale", "amount": 50000, "earnings": 10000, "currency": "usd", "status": "pending", "invoiceId": null, "description": null, "quantity": 1, "userId": "cludszk1h0000wmd2e0ea2b0p", "createdAt": "2025-07-16T10:48:14.722Z", "updatedAt": "2025-07-16T10:48:14.960Z", "partner": { "id": "pn_1K06X6FX2GRB31NCM2VVCGJ72", "name": "Matthew Hayden", "email": "matthew@example.com", "image": null, "payoutsEnabledAt": null, "country": "US", "totalClicks": 50, "totalLeads": 15, "totalConversions": 10, "totalSales": 10, "totalSaleAmount": 100000, "totalCommissions": 50000 }, "customer": { "id": "cus_1K09DJDEACR47NPYC93RM43WF", "externalId": "TaMD05AnuyqeI", "name": "David", "email": "david@example.com", "avatar": null, "country": "US", "sales": 1, "saleAmount": 50000, "createdAt": "2025-07-16T10:48:01.739Z" } } } ``` ## 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%3A-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', customerExternalId: 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". | | `customerExternalId` | **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 completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions: * **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales. Time-series line chart * **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) 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://app.dub.co/events) 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%3A-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", customerExternalId: 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". | | `customerExternalId` | **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 completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions: * **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales. Time-series line chart * **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) 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://app.dub.co/events) 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%3A-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 completed the setup, all your tracked conversions will show up in [Dub Analytics](https://dub.co/analytics). We provide 3 different views to help you understand your conversions: * **Time-series**: A [time-series view](https://app.dub.co/dub/analytics?view=timeseries) of the number clicks, leads and sales. Time-series line chart * **Funnel chart**: A [funnel chart view](http://app.dub.co/analytics?view=funnel) 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://app.dub.co/events) 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%3A-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: