Repository: go-acme/lego (language: go)

Issue:
Support for provider: Websupport
### Welcome

- [X] Yes, I've searched similar issues on GitHub and didn't find any.
- [X] Yes, the DNS provider exposes a public API.
- [X] Yes, I know that the lego maintainers don't have an account in all DNS providers in the world.
- [ ] Yes, I'm able to create a pull request and be able to maintain the implementation.
- [X] Yes, I'm able to test an implementation if someone creates a pull request to add the support of this DNS provider.

### How do you use lego?

Library

### Link to the DNS provider

https://websupport.sk/

### Link to the API documentation

https://rest.websupport.sk/docs/v1.zone

### Additional Notes

Please, implement this provider. I can help you or give you some account to work on this. I would love to use it with Treafik 👏


Discussion / hints:
Hello @dominikbullo,

I'm working on this, can you give me apiKey and a secret to working on this more easily?
How would you like to share them securely?

I already created a client based on the API documentation but I'm not sure about some elements in the response examples (the error field seems to be an array or an object or both)


Fix patch:
diff --git a/README.md b/README.md
index 80f2f0130a..14ce618485 100644
--- a/README.md
+++ b/README.md
@@ -75,8 +75,8 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns).
 | [Sonic](https://go-acme.github.io/lego/dns/sonic/)                              | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/)                      | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/)           | [TransIP](https://go-acme.github.io/lego/dns/transip/)                          |
 | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/)                   | [Ultradns](https://go-acme.github.io/lego/dns/ultradns/)                        | [Variomedia](https://go-acme.github.io/lego/dns/variomedia/)                    | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/)                          |
 | [Vercel](https://go-acme.github.io/lego/dns/vercel/)                            | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/)                 | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/)                        | [VK Cloud](https://go-acme.github.io/lego/dns/vkcloud/)                         |
-| [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            | [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              | [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              | [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/)                 |
-| [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/)                        | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/)                           | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/)                            |                                                                                 |
+| [Vscale](https://go-acme.github.io/lego/dns/vscale/)                            | [Vultr](https://go-acme.github.io/lego/dns/vultr/)                              | [Websupport](https://go-acme.github.io/lego/dns/websupport/)                    | [WEDOS](https://go-acme.github.io/lego/dns/wedos/)                              |
+| [Yandex Cloud](https://go-acme.github.io/lego/dns/yandexcloud/)                 | [Yandex PDD](https://go-acme.github.io/lego/dns/yandex/)                        | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/)                           | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/)                            |
 
 <!-- END DNS PROVIDERS LIST -->
 
diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go
index cb4e8f1491..431ea7b2cc 100644
--- a/cmd/zz_gen_cmd_dnshelp.go
+++ b/cmd/zz_gen_cmd_dnshelp.go
@@ -119,6 +119,7 @@ func allDNSCodes() string {
 		"vkcloud",
 		"vscale",
 		"vultr",
+		"websupport",
 		"wedos",
 		"yandex",
 		"yandexcloud",
@@ -2367,6 +2368,28 @@ func displayDNSHelp(w io.Writer, name string) error {
 		ew.writeln()
 		ew.writeln(`More information: https://go-acme.github.io/lego/dns/vultr`)
 
+	case "websupport":
+		// generated from: providers/dns/websupport/websupport.toml
+		ew.writeln(`Configuration for Websupport.`)
+		ew.writeln(`Code:	'websupport'`)
+		ew.writeln(`Since:	'v4.10.0'`)
+		ew.writeln()
+
+		ew.writeln(`Credentials:`)
+		ew.writeln(`	- "WEBSUPPORT_API_KEY":	API key`)
+		ew.writeln(`	- "WEBSUPPORT_SECRET":	API secret`)
+		ew.writeln()
+
+		ew.writeln(`Additional Configuration:`)
+		ew.writeln(`	- "WEBSUPPORT_HTTP_TIMEOUT":	API request timeout`)
+		ew.writeln(`	- "WEBSUPPORT_POLLING_INTERVAL":	Time between DNS propagation check`)
+		ew.writeln(`	- "WEBSUPPORT_PROPAGATION_TIMEOUT":	Maximum waiting time for DNS propagation`)
+		ew.writeln(`	- "WEBSUPPORT_SEQUENCE_INTERVAL":	Time between sequential requests`)
+		ew.writeln(`	- "WEBSUPPORT_TTL":	The TTL of the TXT record used for the DNS challenge`)
+
+		ew.writeln()
+		ew.writeln(`More information: https://go-acme.github.io/lego/dns/websupport`)
+
 	case "wedos":
 		// generated from: providers/dns/wedos/wedos.toml
 		ew.writeln(`Configuration for WEDOS.`)
diff --git a/docs/content/dns/zz_gen_websupport.md b/docs/content/dns/zz_gen_websupport.md
new file mode 100644
index 0000000000..cf850e58eb
--- /dev/null
+++ b/docs/content/dns/zz_gen_websupport.md
@@ -0,0 +1,70 @@
+---
+title: "Websupport"
+date: 2019-03-03T16:39:46+01:00
+draft: false
+slug: websupport
+dnsprovider:
+  since:    "v4.10.0"
+  code:     "websupport"
+  url:      "https://websupport.sk"
+---
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/websupport/websupport.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+
+
+Configuration for [Websupport](https://websupport.sk).
+
+
+<!--more-->
+
+- Code: `websupport`
+- Since: v4.10.0
+
+
+Here is an example bash command using the Websupport provider:
+
+```bash
+WEBSUPPORT_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \
+WEBSUPPORT_SECRET="yyyyyyyyyyyyyyyyyyyyy" \
+lego --email myemail@example.com --dns websupport --domains my.example.org run
+```
+
+
+
+
+## Credentials
+
+| Environment Variable Name | Description |
+|-----------------------|-------------|
+| `WEBSUPPORT_API_KEY` | API key |
+| `WEBSUPPORT_SECRET` | API secret |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here]({{< ref "dns#configuration-and-credentials" >}}).
+
+
+## Additional Configuration
+
+| Environment Variable Name | Description |
+|--------------------------------|-------------|
+| `WEBSUPPORT_HTTP_TIMEOUT` | API request timeout |
+| `WEBSUPPORT_POLLING_INTERVAL` | Time between DNS propagation check |
+| `WEBSUPPORT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
+| `WEBSUPPORT_SEQUENCE_INTERVAL` | Time between sequential requests |
+| `WEBSUPPORT_TTL` | The TTL of the TXT record used for the DNS challenge |
+
+The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
+More information [here]({{< ref "dns#configuration-and-credentials" >}}).
+
+
+
+
+## More information
+
+- [API documentation](https://rest.websupport.sk/docs/v1.zone)
+
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
+<!-- providers/dns/websupport/websupport.toml -->
+<!-- THIS DOCUMENTATION IS AUTO-GENERATED. PLEASE DO NOT EDIT. -->
diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml
index d9926c2ad5..f526b603d9 100644
--- a/docs/data/zz_cli_help.toml
+++ b/docs/data/zz_cli_help.toml
@@ -125,7 +125,7 @@ To display the documentation for a specific DNS provider, run:
   $ lego dnshelp -c code
 
 Supported DNS providers:
-  acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, bindman, bluecat, checkdomain, civo, clouddns, cloudflare, cloudns, cloudxns, conoha, constellix, desec, designate, digitalocean, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, hetzner, hostingde, hosttech, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, ns1, oraclecloud, otc, ovh, pdns, porkbun, rackspace, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, servercow, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, wedos, yandex, yandexcloud, zoneee, zonomi
+  acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, bindman, bluecat, checkdomain, civo, clouddns, cloudflare, cloudns, cloudxns, conoha, constellix, desec, designate, digitalocean, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, epik, exec, exoscale, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, hetzner, hostingde, hosttech, httpreq, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, iwantmyname, joker, liara, lightsail, linode, liquidweb, loopia, luadns, manual, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, ns1, oraclecloud, otc, ovh, pdns, porkbun, rackspace, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, servercow, simply, sonic, stackpath, tencentcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, vscale, vultr, websupport, wedos, yandex, yandexcloud, zoneee, zonomi
 
 More information: https://go-acme.github.io/lego/dns
 """
diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go
index a0343fb784..2d7b7c1c2c 100644
--- a/providers/dns/dns_providers.go
+++ b/providers/dns/dns_providers.go
@@ -110,6 +110,7 @@ import (
 	"github.com/go-acme/lego/v4/providers/dns/vkcloud"
 	"github.com/go-acme/lego/v4/providers/dns/vscale"
 	"github.com/go-acme/lego/v4/providers/dns/vultr"
+	"github.com/go-acme/lego/v4/providers/dns/websupport"
 	"github.com/go-acme/lego/v4/providers/dns/wedos"
 	"github.com/go-acme/lego/v4/providers/dns/yandex"
 	"github.com/go-acme/lego/v4/providers/dns/yandexcloud"
@@ -328,10 +329,12 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
 		return vinyldns.NewDNSProvider()
 	case "vkcloud":
 		return vkcloud.NewDNSProvider()
-	case "vultr":
-		return vultr.NewDNSProvider()
 	case "vscale":
 		return vscale.NewDNSProvider()
+	case "vultr":
+		return vultr.NewDNSProvider()
+	case "websupport":
+		return websupport.NewDNSProvider()
 	case "wedos":
 		return wedos.NewDNSProvider()
 	case "yandex":
diff --git a/providers/dns/websupport/internal/client.go b/providers/dns/websupport/internal/client.go
new file mode 100644
index 0000000000..1dc2d42b2f
--- /dev/null
+++ b/providers/dns/websupport/internal/client.go
@@ -0,0 +1,258 @@
+package internal
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/sha1"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"path"
+	"strconv"
+	"time"
+)
+
+const defaultBaseURL = "https://rest.websupport.sk"
+
+// StatusSuccess expected status text when success.
+const StatusSuccess = "success"
+
+// Client a Websupport DNS API client.
+type Client struct {
+	apiKey     string
+	secretKey  string
+	BaseURL    string
+	HTTPClient *http.Client
+}
+
+// NewClient creates a new Client.
+func NewClient(apiKey, secretKey string) (*Client, error) {
+	if apiKey == "" || secretKey == "" {
+		return nil, errors.New("credentials missing")
+	}
+
+	return &Client{
+		apiKey:     apiKey,
+		secretKey:  secretKey,
+		BaseURL:    defaultBaseURL,
+		HTTPClient: &http.Client{Timeout: 10 * time.Second},
+	}, nil
+}
+
+// GetUser gets a user detail.
+// https://rest.websupport.sk/docs/v1.user#user
+func (c *Client) GetUser(userID string) (*User, error) {
+	baseURL, err := url.Parse(c.BaseURL)
+	if err != nil {
+		return nil, fmt.Errorf("base url parsing: %w", err)
+	}
+
+	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", userID))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
+	}
+
+	req, err := http.NewRequest(http.MethodGet, endpoint.String(), http.NoBody)
+	if err != nil {
+		return nil, fmt.Errorf("request payload: %w", err)
+	}
+
+	result := &User{}
+
+	err = c.do(req, result)
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// ListRecords lists all records.
+// https://rest.websupport.sk/docs/v1.zone#records
+func (c *Client) ListRecords(domainName string) (*ListResponse, error) {
+	baseURL, err := url.Parse(c.BaseURL)
+	if err != nil {
+		return nil, fmt.Errorf("base url parsing: %w", err)
+	}
+
+	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", "self", "zone", domainName, "record"))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
+	}
+
+	req, err := http.NewRequest(http.MethodGet, endpoint.String(), http.NoBody)
+	if err != nil {
+		return nil, fmt.Errorf("request payload: %w", err)
+	}
+
+	result := &ListResponse{}
+
+	err = c.do(req, result)
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// GetRecords gets a DNS record.
+func (c *Client) GetRecords(domainName string, recordID int) (*Record, error) {
+	baseURL, err := url.Parse(c.BaseURL)
+	if err != nil {
+		return nil, fmt.Errorf("base url parsing: %w", err)
+	}
+
+	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", "self", "zone", domainName, "record", strconv.Itoa(recordID)))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
+	}
+
+	req, err := http.NewRequest(http.MethodGet, endpoint.String(), http.NoBody)
+	if err != nil {
+		return nil, err
+	}
+
+	result := &Record{}
+
+	err = c.do(req, result)
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// AddRecord adds a DNS record.
+// https://rest.websupport.sk/docs/v1.zone#post-record
+func (c *Client) AddRecord(domainName string, record Record) (*Response, error) {
+	baseURL, err := url.Parse(c.BaseURL)
+	if err != nil {
+		return nil, fmt.Errorf("base url parsing: %w", err)
+	}
+
+	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", "self", "zone", domainName, "record"))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
+	}
+
+	payload, err := json.Marshal(record)
+	if err != nil {
+		return nil, fmt.Errorf("request payload: %w", err)
+	}
+
+	req, err := http.NewRequest(http.MethodPost, endpoint.String(), bytes.NewReader(payload))
+	if err != nil {
+		return nil, err
+	}
+
+	result := &Response{}
+
+	err = c.do(req, result)
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// DeleteRecord deletes a DNS record.
+// https://rest.websupport.sk/docs/v1.zone#delete-record
+func (c *Client) DeleteRecord(domainName string, recordID int) (*Response, error) {
+	baseURL, err := url.Parse(c.BaseURL)
+	if err != nil {
+		return nil, fmt.Errorf("base url parsing: %w", err)
+	}
+
+	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, "v1", "user", "self", "zone", domainName, "record", strconv.Itoa(recordID)))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse endpoint: %w", err)
+	}
+
+	req, err := http.NewRequest(http.MethodDelete, endpoint.String(), http.NoBody)
+	if err != nil {
+		return nil, fmt.Errorf("request payload: %w", err)
+	}
+
+	result := &Response{}
+
+	err = c.do(req, result)
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (c *Client) do(req *http.Request, result any) error {
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("Accept", "application/json")
+	req.Header.Set("Accept-Language", "en_us")
+
+	location, err := time.LoadLocation("GMT")
+	if err != nil {
+		return fmt.Errorf("time location: %w", err)
+	}
+
+	err = c.sign(req, time.Now().In(location))
+	if err != nil {
+		return fmt.Errorf("signature: %w", err)
+	}
+
+	resp, err := c.HTTPClient.Do(req)
+	if err != nil {
+		return err
+	}
+
+	defer func() { _ = resp.Body.Close() }()
+
+	if resp.StatusCode > http.StatusBadRequest {
+		all, _ := io.ReadAll(resp.Body)
+
+		var e APIError
+		err = json.Unmarshal(all, &e)
+		if err != nil {
+			return fmt.Errorf("%d: %s", resp.StatusCode, string(all))
+		}
+
+		return &e
+	}
+
+	all, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return fmt.Errorf("read response body: %w", err)
+	}
+
+	err = json.Unmarshal(all, result)
+	if err != nil {
+		return fmt.Errorf("unmarshal response body: %w", err)
+	}
+
+	return nil
+}
+
+func (c *Client) sign(req *http.Request, now time.Time) error {
+	if req.URL.Path == "" {
+		req.URL.Path += "/"
+	}
+
+	canonicalRequest := fmt.Sprintf("%s %s %d", req.Method, req.URL.Path, now.Unix())
+
+	mac := hmac.New(sha1.New, []byte(c.secretKey))
+	_, err := mac.Write([]byte(canonicalRequest))
+	if err != nil {
+		return err
+	}
+
+	hashed := mac.Sum(nil)
+	signature := hex.EncodeToString(hashed)
+
+	req.SetBasicAuth(c.apiKey, signature)
+
+	req.Header.Set("Date", now.Format(time.RFC3339))
+
+	return nil
+}
diff --git a/providers/dns/websupport/internal/fixtures/add-record-error-400.json b/providers/dns/websupport/internal/fixtures/add-record-error-400.json
new file mode 100644
index 0000000000..b60b7989a4
--- /dev/null
+++ b/providers/dns/websupport/internal/fixtures/add-record-error-400.json
@@ -0,0 +1,26 @@
+{
+  "status": "error",
+  "item": {
+    "id": null,
+    "type": "A",
+    "name": "something bad !@#$%^&*(",
+    "content": "123.456.789.123",
+    "ttl": 600,
+    "prio": null,
+    "weight": null,
+    "port": null,
+    "zone": {
+      "id": 1,
+      "name": "scaledo.com",
+      "updateTime": 1381169608
+    }
+  },
+  "errors": {
+    "content": [
+      "Wrong IP address format"
+    ],
+    "name": [
+      "Invalid input."
+    ]
+  }
+}
diff --git a/providers/dns/websupport/internal/fixtures/add-record-error-404.json b/providers/dns/websupport/internal/fixtures/add-record-error-404.json
new file mode 100644
index 0000000000..837b5392a0
--- /dev/null
+++ b/providers/dns/websupport/internal/fixtures/add-record-error-404.json
@@ -0,0 +1,4 @@
+{
+  "code": 404,
+  "message": "Zone not found"
+}
diff --git a/providers/dns/websupport/internal/fixtures/add-record.json b/providers/dns/websupport/internal/fixtures/add-record.json
new file mode 100644
index 0000000000..5990cf3d3e
--- /dev/null
+++ b/providers/dns/websupport/internal/fixtures/add-record.json
@@ -0,0 +1,19 @@
+{
+  "status": "success",
+  "item": {
+    "id": 4,
+    "type": "A",
+    "name": "@",
+    "content": "1.2.3.4",
+    "ttl": 600,
+    "prio": null,
+    "weight": null,
+    "port": null,
+    "zone": {
+      "id": 1,
+      "name": "example.com",
+      "updateTime": 1381169608
+    }
+  },
+  "errors": []
+}
diff --git a/providers/dns/websupport/internal/fixtures/delete-record-error-404.json b/providers/dns/websupport/internal/fixtures/delete-record-error-404.json
new file mode 100644
index 0000000000..e66fa5dc60
--- /dev/null
+++ b/providers/dns/websupport/internal/fixtures/delete-record-error-404.json
@@ -0,0 +1,4 @@
+{
+  "code": 404,
+  "message": "Record not found"
+}
diff --git a/providers/dns/websupport/internal/fixtures/delete-record.json b/providers/dns/websupport/internal/fixtures/delete-record.json
new file mode 100644
index 0000000000..8fdff82cbe
--- /dev/null
+++ b/providers/dns/websupport/internal/fixtures/delete-record.json
@@ -0,0 +1,19 @@
+{
+  "status": "success",
+  "item": {
+    "id": 1,
+    "type": "A",
+    "name": "@",
+    "content": "1.2.3.4",
+    "ttl": 600,
+    "prio": null,
+    "weight": null,
+    "port": null,
+    "zone": {
+      "id": 1,
+      "name": "scaledo.com",
+      "updateTime": 1381316081
+    }
+  },
+  "errors": []
+}
diff --git a/providers/dns/websupport/internal/fixtures/get-record.json b/providers/dns/websupport/internal/fixtures/get-record.json
new file mode 100644
index 0000000000..d1bd2f1373
--- /dev/null
+++ b/providers/dns/websupport/internal/fixtures/get-record.json
@@ -0,0 +1,12 @@
+{
+  "id": 69966832,
+  "type": "TXT",
+  "name": "_acme-challenge",
+  "content": "txttxttxt",
+  "ttl": 600,
+  "zone": {
+    "id": 0,
+    "name": "example.com",
+    "updateTime": 1675240207
+  }
+}
diff --git a/providers/dns/websupport/internal/fixtures/get-user.json b/providers/dns/websupport/internal/fixtures/get-user.json
new file mode 100644
index 0000000000..ad49787550
--- /dev/null
+++ b/providers/dns/websupport/internal/fixtures/get-user.json
@@ -0,0 +1,36 @@
+{
+  "id": 987654321,
+  "login": "lego@example.com",
+  "parentId": null,
+  "active": true,
+  "createTime": 1675237889,
+  "group": "users",
+  "email": "lego@example.com",
+  "phone": "+123456789",
+  "contactPerson": "",
+  "awaitingTosConfirmation": "1",
+  "userLanguage": "sk-SK",
+  "credit": 0,
+  "verifyUrl": "https:\/\/rest.websupport.sk\/v1\/user\/verify\/key\/xxx",
+  "billing": [
+    {
+      "id": 1099970,
+      "profile": "default",
+      "isDefault": true,
+      "name": "asdsdfs",
+      "city": "\u017dilina",
+      "street": "asddfsdfsdf",
+      "companyRegId": null,
+      "taxId": null,
+      "vatId": null,
+      "zip": "01234",
+      "country": "sk",
+      "isic": ""
+    }
+  ],
+  "market": {
+    "name": "Slovakia",
+    "identifier": "sk",
+    "currency": "EUR"
+  }
+}
diff --git a/providers/dns/websupport/internal/fixtures/list-records.json b/providers/dns/websupport/internal/fixtures/list-records.json
new file mode 100644
index 0000000000..d0ad57dc98
--- /dev/null
+++ b/providers/dns/websupport/internal/fixtures/list-records.json
@@ -0,0 +1,29 @@
+{
+  "items": [
+    {
+      "id": 1,
+      "type": "A",
+      "name": "@",
+      "content": "37.9.169.99",
+      "ttl": 600,
+      "prio": null,
+      "weight": null,
+      "port": null
+    },
+    {
+      "id": 2,
+      "type": "NS",
+      "name": "@",
+      "content": "ns1.scaledo.com",
+      "ttl": 600,
+      "prio": null,
+      "weight": null,
+      "port": null
+    }
+  ],
+  "pager": {
+    "page": 1,
+    "pagesize": 0,
+    "items": 2
+  }
+}
diff --git a/providers/dns/websupport/internal/types.go b/providers/dns/websupport/internal/types.go
new file mode 100644
index 0000000000..cada90ceb1
--- /dev/null
+++ b/providers/dns/websupport/internal/types.go
@@ -0,0 +1,121 @@
+package internal
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+type APIError struct {
+	Code    int    `json:"code"`
+	Message string `json:"message"`
+}
+
+func (a *APIError) Error() string {
+	return fmt.Sprintf("%d: %s", a.Code, a.Message)
+}
+
+type Record struct {
+	ID      int    `json:"id,omitempty"`
+	Type    string `json:"type,omitempty"`
+	Name    string `json:"name,omitempty"` // subdomain name or @ if you don't want subdomain
+	Content string `json:"content,omitempty"`
+	TTL     int    `json:"ttl,omitempty"` // default 600
+	Zone    *Zone  `json:"zone"`
+}
+
+type Zone struct {
+	ID         int    `json:"id"`
+	Name       string `json:"name"`
+	UpdateTime int    `json:"updateTime"`
+}
+
+type Response struct {
+	Status string          `json:"status"`
+	Item   *Record         `json:"item"`
+	Errors json.RawMessage `json:"errors"`
+}
+
+type ListResponse struct {
+	Items []Record `json:"items"`
+	Pager Pager    `json:"pager"`
+}
+
+type Pager struct {
+	Page     int `json:"page"`
+	PageSize int `json:"pagesize"`
+	Items    int `json:"items"`
+}
+
+type Errors struct {
+	Name    []string `json:"name"`
+	Content []string `json:"content"`
+}
+
+func (e *Errors) Error() string {
+	var msg string
+	for i, s := range e.Name {
+		msg += s
+		if i != len(e.Name)-1 {
+			msg += ": "
+		}
+	}
+
+	for i, s := range e.Content {
+		msg += s
+		if i != len(e.Content)-1 {
+			msg += ": "
+		}
+	}
+
+	return msg
+}
+
+// ParseError extract error from Response.
+func ParseError(resp *Response) error {
+	var apiError Errors
+	err := json.Unmarshal(resp.Errors, &apiError)
+	if err != nil {
+		return err
+	}
+
+	return &apiError
+}
+
+type User struct {
+	ID                      int       `json:"id"`
+	Login                   string    `json:"login"`
+	ParentID                int       `json:"parentId"`
+	Active                  bool      `json:"active"`
+	CreateTime              int       `json:"createTime"`
+	Group                   string    `json:"group"`
+	Email                   string    `json:"email"`
+	Phone                   string    `json:"phone"`
+	ContactPerson           string    `json:"contactPerson"`
+	AwaitingTosConfirmation string    `json:"awaitingTosConfirmation"`
+	UserLanguage            string    `json:"userLanguage"`
+	Credit                  int       `json:"credit"`
+	VerifyURL               string    `json:"verifyUrl"`
+	Billing                 []Billing `json:"billing"`
+	Market                  Market    `json:"market"`
+}
+
+type Billing struct {
+	ID           int    `json:"id"`
+	Profile      string `json:"profile"`
+	IsDefault    bool   `json:"isDefault"`
+	Name         string `json:"name"`
+	City         string `json:"city"`
+	Street       string `json:"street"`
+	CompanyRegID int    `json:"companyRegId"`
+	TaxID        int    `json:"taxId"`
+	VatID        int    `json:"vatId"`
+	Zip          string `json:"zip"`
+	Country      string `json:"country"`
+	ISIC         string `json:"isic"`
+}
+
+type Market struct {
+	Name       string `json:"name"`
+	Identifier string `json:"identifier"`
+	Currency   string `json:"currency"`
+}
diff --git a/providers/dns/websupport/websupport.go b/providers/dns/websupport/websupport.go
new file mode 100644
index 0000000000..1af3618f51
--- /dev/null
+++ b/providers/dns/websupport/websupport.go
@@ -0,0 +1,194 @@
+// Package websupport implements a DNS provider for solving the DNS-01 challenge using Websupport.
+package websupport
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"sync"
+	"time"
+
+	"github.com/go-acme/lego/v4/challenge/dns01"
+	"github.com/go-acme/lego/v4/platform/config/env"
+	"github.com/go-acme/lego/v4/providers/dns/websupport/internal"
+)
+
+const defaultTTL = 600
+
+// Environment variables names.
+const (
+	envNamespace = "WEBSUPPORT_"
+
+	EnvAPIKey = envNamespace + "API_KEY"
+	EnvSecret = envNamespace + "SECRET"
+
+	EnvTTL                = envNamespace + "TTL"
+	EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
+	EnvPollingInterval    = envNamespace + "POLLING_INTERVAL"
+	EnvHTTPTimeout        = envNamespace + "HTTP_TIMEOUT"
+	EnvSequenceInterval   = envNamespace + "SEQUENCE_INTERVAL"
+)
+
+// Config is used to configure the creation of the DNSProvider.
+type Config struct {
+	APIKey string
+	Secret string
+
+	PropagationTimeout time.Duration
+	PollingInterval    time.Duration
+	SequenceInterval   time.Duration
+	TTL                int
+	HTTPClient         *http.Client
+}
+
+// NewDefaultConfig returns a default configuration for the DNSProvider.
+func NewDefaultConfig() *Config {
+	return &Config{
+		TTL:                env.GetOrDefaultInt(EnvTTL, defaultTTL),
+		PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
+		PollingInterval:    env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
+		SequenceInterval:   env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout),
+		HTTPClient: &http.Client{
+			Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
+		},
+	}
+}
+
+// DNSProvider implements the challenge.Provider interface.
+type DNSProvider struct {
+	config *Config
+	client *internal.Client
+
+	recordIDs   map[string]int
+	recordIDsMu sync.Mutex
+}
+
+// NewDNSProvider returns a DNSProvider instance configured for Websupport.
+// Credentials must be passed in the environment variables: WEBSUPPORT_API_KEY, WEBSUPPORT_SECRET.
+func NewDNSProvider() (*DNSProvider, error) {
+	values, err := env.Get(EnvAPIKey, EnvSecret)
+	if err != nil {
+		return nil, fmt.Errorf("websupport: %w", err)
+	}
+
+	config := NewDefaultConfig()
+	config.APIKey = values[EnvAPIKey]
+	config.Secret = values[EnvSecret]
+
+	return NewDNSProviderConfig(config)
+}
+
+// NewDNSProviderConfig return a DNSProvider instance configured for Websupport.
+func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
+	if config == nil {
+		return nil, errors.New("websupport: the configuration of the DNS provider is nil")
+	}
+
+	client, err := internal.NewClient(config.APIKey, config.Secret)
+	if err != nil {
+		return nil, fmt.Errorf("websupport: %w", err)
+	}
+
+	if config.HTTPClient != nil {
+		client.HTTPClient = config.HTTPClient
+	}
+
+	return &DNSProvider{
+		config:    config,
+		client:    client,
+		recordIDs: make(map[string]int),
+	}, nil
+}
+
+// Present creates a TXT record using the specified parameters.
+func (d *DNSProvider) Present(domain, token, keyAuth string) error {
+	fqdn, value := dns01.GetRecord(domain, keyAuth)
+
+	authZone, err := dns01.FindZoneByFqdn(fqdn)
+	if err != nil {
+		return fmt.Errorf("websupport: %w", err)
+	}
+
+	subDomain, err := dns01.ExtractSubDomain(fqdn, authZone)
+	if err != nil {
+		return fmt.Errorf("websupport: %w", err)
+	}
+
+	record := internal.Record{
+		Type:    "TXT",
+		Name:    subDomain,
+		Content: value,
+		TTL:     d.config.TTL,
+	}
+
+	resp, err := d.client.AddRecord(dns01.UnFqdn(authZone), record)
+	if err != nil {
+		return fmt.Errorf("websupport: add record: %w", err)
+	}
+
+	if resp.Status == internal.StatusSuccess {
+		d.recordIDsMu.Lock()
+		d.recordIDs[token] = resp.Item.ID
+		d.recordIDsMu.Unlock()
+
+		return nil
+	}
+
+	err = internal.ParseError(resp)
+	if err != nil {
+		return fmt.Errorf("websupport: %w", err)
+	}
+
+	return nil
+}
+
+// CleanUp removes the TXT record matching the specified parameters.
+func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
+	fqdn, _ := dns01.GetRecord(domain, keyAuth)
+
+	authZone, err := dns01.FindZoneByFqdn(fqdn)
+	if err != nil {
+		return fmt.Errorf("websupport: %w", err)
+	}
+
+	// gets the record's unique ID
+	d.recordIDsMu.Lock()
+	recordID, ok := d.recordIDs[token]
+	d.recordIDsMu.Unlock()
+	if !ok {
+		return fmt.Errorf("websupport: unknown record ID for '%s' '%s'", fqdn, token)
+	}
+
+	resp, err := d.client.DeleteRecord(dns01.UnFqdn(authZone), recordID)
+	if err != nil {
+		return fmt.Errorf("websupport: delete record: %w", err)
+	}
+
+	// deletes record ID from map
+	d.recordIDsMu.Lock()
+	delete(d.recordIDs, token)
+	d.recordIDsMu.Unlock()
+
+	if resp.Status == internal.StatusSuccess {
+		return nil
+	}
+
+	err = internal.ParseError(resp)
+	if err != nil {
+		return fmt.Errorf("websupport: %w", err)
+	}
+
+	return nil
+}
+
+// Timeout returns the timeout and interval to use when checking for DNS propagation.
+// Adjusting here to cope with spikes in propagation times.
+func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
+	return d.config.PropagationTimeout, d.config.PollingInterval
+}
+
+// Sequential All DNS challenges for this provider will be resolved sequentially.
+// Returns the interval between each iteration.
+func (d *DNSProvider) Sequential() time.Duration {
+	return d.config.SequenceInterval
+}
diff --git a/providers/dns/websupport/websupport.toml b/providers/dns/websupport/websupport.toml
new file mode 100644
index 0000000000..8eb32fbbb8
--- /dev/null
+++ b/providers/dns/websupport/websupport.toml
@@ -0,0 +1,25 @@
+Name = "Websupport"
+Description = ''''''
+URL = "https://websupport.sk"
+Code = "websupport"
+Since = "v4.10.0"
+
+Example = '''
+WEBSUPPORT_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \
+WEBSUPPORT_SECRET="yyyyyyyyyyyyyyyyyyyyy" \
+lego --email myemail@example.com --dns websupport --domains my.example.org run
+'''
+
+[Configuration]
+  [Configuration.Credentials]
+    WEBSUPPORT_API_KEY = "API key"
+    WEBSUPPORT_SECRET = "API secret"
+  [Configuration.Additional]
+    WEBSUPPORT_POLLING_INTERVAL = "Time between DNS propagation check"
+    WEBSUPPORT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
+    WEBSUPPORT_SEQUENCE_INTERVAL = "Time between sequential requests"
+    WEBSUPPORT_TTL = "The TTL of the TXT record used for the DNS challenge"
+    WEBSUPPORT_HTTP_TIMEOUT = "API request timeout"
+
+[Links]
+  API = "https://rest.websupport.sk/docs/v1.zone"


Test patch:
diff --git a/providers/dns/websupport/internal/client_test.go b/providers/dns/websupport/internal/client_test.go
new file mode 100644
index 0000000000..312d74f388
--- /dev/null
+++ b/providers/dns/websupport/internal/client_test.go
@@ -0,0 +1,232 @@
+package internal
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func setupTest(t *testing.T, method, pattern string, status int, file string) *Client {
+	t.Helper()
+
+	mux := http.NewServeMux()
+	server := httptest.NewServer(mux)
+	t.Cleanup(server.Close)
+
+	mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) {
+		if req.Method != method {
+			http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest)
+			return
+		}
+
+		open, err := os.Open(file)
+		if err != nil {
+			http.Error(rw, err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		defer func() { _ = open.Close() }()
+
+		rw.WriteHeader(status)
+		_, err = io.Copy(rw, open)
+		if err != nil {
+			http.Error(rw, err.Error(), http.StatusInternalServerError)
+			return
+		}
+	})
+
+	client, err := NewClient("apiKey", "secretKey")
+	require.NoError(t, err)
+
+	client.HTTPClient = server.Client()
+	client.BaseURL = server.URL
+
+	return client
+}
+
+func TestClient_GetUser(t *testing.T) {
+	client := setupTest(t, http.MethodGet, "/v1/user/self", http.StatusOK, "./fixtures/get-user.json")
+
+	user, err := client.GetUser("self")
+	require.NoError(t, err)
+
+	expected := &User{
+		ID:                      987654321,
+		Login:                   "lego@example.com",
+		Active:                  true,
+		CreateTime:              1675237889,
+		Group:                   "users",
+		Email:                   "lego@example.com",
+		Phone:                   "+123456789",
+		ContactPerson:           "",
+		AwaitingTosConfirmation: "1",
+		UserLanguage:            "sk-SK",
+		Credit:                  0,
+		VerifyURL:               "https://rest.websupport.sk/v1/user/verify/key/xxx",
+		Billing: []Billing{{
+			ID:        1099970,
+			Profile:   "default",
+			IsDefault: true,
+			Name:      "asdsdfs",
+			City:      "Žilina",
+			Street:    "asddfsdfsdf",
+			Zip:       "01234",
+			Country:   "sk",
+		}},
+		Market: Market{Name: "Slovakia", Identifier: "sk", Currency: "EUR"},
+	}
+
+	assert.Equal(t, expected, user)
+}
+
+func TestClient_ListRecords(t *testing.T) {
+	client := setupTest(t, http.MethodGet, "/v1/user/self/zone/example.com/record", http.StatusOK, "./fixtures/list-records.json")
+
+	resp, err := client.ListRecords("example.com")
+	require.NoError(t, err)
+
+	expected := &ListResponse{
+		Items: []Record{
+			{
+				ID:      1,
+				Type:    "A",
+				Name:    "@",
+				Content: "37.9.169.99",
+				TTL:     600,
+			}, {
+				ID:      2,
+				Type:    "NS",
+				Name:    "@",
+				Content: "ns1.scaledo.com",
+				TTL:     600,
+			},
+		},
+		Pager: Pager{Page: 1, PageSize: 0, Items: 2},
+	}
+
+	assert.Equal(t, expected, resp)
+}
+
+func TestClient_AddRecord(t *testing.T) {
+	client := setupTest(t, http.MethodPost, "/v1/user/self/zone/example.com/record", http.StatusCreated, "./fixtures/add-record.json")
+
+	record := Record{
+		Type:    "TXT",
+		Name:    "_acme-challenge",
+		Content: "txttxttxt",
+		TTL:     600,
+	}
+
+	resp, err := client.AddRecord("example.com", record)
+	require.NoError(t, err)
+
+	expected := &Response{
+		Status: "success",
+		Item: &Record{
+			ID:      4,
+			Type:    "A",
+			Name:    "@",
+			Content: "1.2.3.4",
+			TTL:     600,
+			Zone: &Zone{
+				ID:         1,
+				Name:       "example.com",
+				UpdateTime: 1381169608,
+			},
+		},
+		Errors: json.RawMessage("[]"),
+	}
+
+	assert.Equal(t, expected, resp)
+}
+
+func TestClient_AddRecord_error_400(t *testing.T) {
+	client := setupTest(t, http.MethodPost, "/v1/user/self/zone/example.com/record", http.StatusBadRequest, "./fixtures/add-record-error-400.json")
+
+	record := Record{
+		Type:    "TXT",
+		Name:    "_acme-challenge",
+		Content: "txttxttxt",
+		TTL:     600,
+	}
+
+	resp, err := client.AddRecord("example.com", record)
+	require.NoError(t, err)
+
+	assert.Equal(t, "error", resp.Status)
+
+	expectedRecord := &Record{
+		ID:      0,
+		Type:    "A",
+		Name:    "something bad !@#$%^&*(",
+		Content: "123.456.789.123",
+		TTL:     600,
+		Zone: &Zone{
+			ID:         1,
+			Name:       "scaledo.com",
+			UpdateTime: 1381169608,
+		},
+	}
+	assert.Equal(t, expectedRecord, resp.Item)
+
+	expected := &Errors{Name: []string{"Invalid input."}, Content: []string{"Wrong IP address format"}}
+	assert.Equal(t, expected, ParseError(resp))
+}
+
+func TestClient_AddRecord_error_404(t *testing.T) {
+	client := setupTest(t, http.MethodPost, "/v1/user/self/zone/example.com/record", http.StatusNotFound, "./fixtures/add-record-error-404.json")
+
+	record := Record{
+		Type:    "TXT",
+		Name:    "_acme-challenge",
+		Content: "txttxttxt",
+		TTL:     600,
+	}
+
+	resp, err := client.AddRecord("example.com", record)
+	require.Error(t, err)
+
+	assert.Nil(t, resp)
+}
+
+func TestClient_DeleteRecord(t *testing.T) {
+	client := setupTest(t, http.MethodDelete, "/v1/user/self/zone/example.com/record/123", http.StatusOK, "./fixtures/delete-record.json")
+
+	resp, err := client.DeleteRecord("example.com", 123)
+	require.NoError(t, err)
+
+	expected := &Response{
+		Status: "success",
+		Item: &Record{
+			ID:      1,
+			Type:    "A",
+			Name:    "@",
+			Content: "1.2.3.4",
+			TTL:     600,
+			Zone: &Zone{
+				ID:         1,
+				Name:       "scaledo.com",
+				UpdateTime: 1381316081,
+			},
+		},
+		Errors: json.RawMessage("[]"),
+	}
+
+	assert.Equal(t, expected, resp)
+}
+
+func TestClient_DeleteRecord_error(t *testing.T) {
+	client := setupTest(t, http.MethodDelete, "/v1/user/self/zone/example.com/record/123", http.StatusNotFound, "./fixtures/delete-record-error-404.json")
+
+	resp, err := client.DeleteRecord("example.com", 123)
+	require.Error(t, err)
+
+	assert.Nil(t, resp)
+}
diff --git a/providers/dns/websupport/websupport_test.go b/providers/dns/websupport/websupport_test.go
new file mode 100644
index 0000000000..e79dd7130e
--- /dev/null
+++ b/providers/dns/websupport/websupport_test.go
@@ -0,0 +1,141 @@
+package websupport
+
+import (
+	"testing"
+
+	"github.com/go-acme/lego/v4/platform/tester"
+	"github.com/stretchr/testify/require"
+)
+
+const envDomain = envNamespace + "DOMAIN"
+
+var envTest = tester.NewEnvTest(EnvAPIKey, EnvSecret).WithDomain(envDomain)
+
+func TestNewDNSProvider(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		envVars  map[string]string
+		expected string
+	}{
+		{
+			desc: "success",
+			envVars: map[string]string{
+				EnvAPIKey: "key",
+				EnvSecret: "secret",
+			},
+		},
+		{
+			desc: "missing API key",
+			envVars: map[string]string{
+				EnvSecret: "secret",
+			},
+			expected: "websupport: some credentials information are missing: WEBSUPPORT_API_KEY",
+		},
+		{
+			desc: "missing secret",
+			envVars: map[string]string{
+				EnvAPIKey: "key",
+			},
+			expected: "websupport: some credentials information are missing: WEBSUPPORT_SECRET",
+		},
+		{
+			desc:     "missing credentials",
+			envVars:  map[string]string{},
+			expected: "websupport: some credentials information are missing: WEBSUPPORT_API_KEY,WEBSUPPORT_SECRET",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			defer envTest.RestoreEnv()
+			envTest.ClearEnv()
+
+			envTest.Apply(test.envVars)
+
+			p, err := NewDNSProvider()
+
+			if test.expected == "" {
+				require.NoError(t, err)
+				require.NotNil(t, p)
+				require.NotNil(t, p.config)
+				require.NotNil(t, p.client)
+			} else {
+				require.EqualError(t, err, test.expected)
+			}
+		})
+	}
+}
+
+func TestNewDNSProviderConfig(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		apiKey   string
+		secret   string
+		expected string
+	}{
+		{
+			desc:   "success",
+			apiKey: "key",
+			secret: "secret",
+		},
+		{
+			desc:     "missing API key",
+			secret:   "secret",
+			expected: "websupport: credentials missing",
+		},
+		{
+			desc:     "missing secret",
+			apiKey:   "key",
+			expected: "websupport: credentials missing",
+		},
+		{
+			desc:     "missing credentials",
+			expected: "websupport: credentials missing",
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			config := NewDefaultConfig()
+			config.APIKey = test.apiKey
+			config.Secret = test.secret
+
+			p, err := NewDNSProviderConfig(config)
+
+			if test.expected == "" {
+				require.NoError(t, err)
+				require.NotNil(t, p)
+				require.NotNil(t, p.config)
+				require.NotNil(t, p.client)
+			} else {
+				require.EqualError(t, err, test.expected)
+			}
+		})
+	}
+}
+
+func TestLivePresent(t *testing.T) {
+	if !envTest.IsLiveTest() {
+		t.Skip("skipping live test")
+	}
+
+	envTest.RestoreEnv()
+	provider, err := NewDNSProvider()
+	require.NoError(t, err)
+
+	err = provider.Present(envTest.GetDomain(), "", "123d==")
+	require.NoError(t, err)
+}
+
+func TestLiveCleanUp(t *testing.T) {
+	if !envTest.IsLiveTest() {
+		t.Skip("skipping live test")
+	}
+
+	envTest.RestoreEnv()
+	provider, err := NewDNSProvider()
+	require.NoError(t, err)
+
+	err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
+	require.NoError(t, err)
+}