# Set up a customer's DNS for your service (DNS Connect)

If your service needs records in a customer's DNS (mail for an email provider, an A record or CNAME for a website host, a verification TXT record), **DNS Connect** lets the customer approve them on Porkbun in one step. You send the customer to Porkbun with the exact records you need. They log in, see what will be added and removed, and approve or deny. Then they come back to you.

A plain-language overview for customers and product teams is at https://porkbun.com/dnsconnect.

> **You never handle the customer's Porkbun password, and you get no standing access.** Each request covers one domain and one set of records, and only after the customer approves it. For changes made by the customer's own code or AI agent, use the API instead (`/dns/create` and friends); DNS Connect is for services acting on someone else's domain.

## How it works

1. You build a request: the domain, the records, and where to send the customer back. You sign it with your client secret, as a JWT.
2. You redirect the customer to `https://porkbun.com/dnsconnect/authorize?request=<jwt>`.
3. The customer logs in to Porkbun with their own 2FA. They see:
   - the records that will be added and removed, including anything removed to make room (for example, a CNAME can't share its name with other records);
   - warnings for risky changes, such as mail moving provider, the website being repointed, ownership tokens for another service, or NS and CAA records;
   - a plain-English review of the change;
   - a clear instruction to approve only if they trust you, and a box they must tick before the **Approve** button enables.
4. On approval, Porkbun saves a restore point, applies the change, and emails the customer a summary with an undo link. The customer then sees a confirmation page on Porkbun listing what changed, with an **Undo** button and a **Continue** button that returns them to your `redirect_uri`.

## 1. Register as a client (one time)

Apply at https://porkbun.com/dnsconnect/register (you need a Porkbun account). You'll be asked for:

- your application's name, as customers should see it (the reviewer may adjust it to match your brand);
- the company behind it, your website and a contact email;
- the exact callback URL(s) you'll use, up to 5. They're matched exactly, with no wildcards. `http://localhost` is allowed for development;
- what your service sets up, and which records it needs.

Every application is reviewed, because customers see your name on the approval page. You'll get an email when it's decided. Your `client_id` is shown as soon as you apply. Once you're approved, collect your **client secret** from the same page. It's shown once; if you lose it, rotate it there, and the old one stops working immediately.

Keep the secret server-side. Anyone holding it can make requests that appear to come from you.

## 2. Build and sign the request

The request is a JWT signed with **HS256** using your client secret. Any JWT library works. Claims:

| Claim | Required | Meaning |
|-------|----------|---------|
| `iss` | yes | Your `client_id` |
| `domain` | yes | The domain to change: lowercase, punycode for IDNs |
| `records` | yes | Array of records (below), at most 25 |
| `redirect_uri` | yes | One of your registered callbacks |
| `iat`, `exp` | yes | Issued-at and expiry (Unix seconds). `exp - iat` may be at most 3600, so the customer has time to log in |
| `state` | recommended | Opaque value echoed back to you. Use it to tie the callback to your session (CSRF) |
| `service` | no | The end service, if you set up DNS on another company's behalf. Shown as "*service* (via *your name*)" |

Each record:

| Field | Required | Meaning |
|-------|----------|---------|
| `type` | yes | `A`, `AAAA`, `CNAME`, `ALIAS`, `MX`, `TXT`, `SRV`, `CAA`, or `NS` (NS below the root only) |
| `host` | yes | Relative name: `""` or `"@"` for the root, `"www"`, `"_dmarc"`. A full name ending in the domain is also accepted |
| `content` | yes | The value |
| `ttl` | no | Seconds, default 600 |
| `prio` | MX/SRV | Priority, default 10 |
| `replace` | no | `true` removes the domain's existing records of the same type at that name first. Use it for MX when moving mail, or for the root A record when moving a website. Without it, records are added alongside what is there |

Rules Porkbun applies for you, so you don't have to:

- **Records already present** are left alone and shown as "already set".
- **CNAME conflicts:** adding a CNAME removes other records at that name, and adding a record where a CNAME exists removes the CNAME. The customer sees both.
- **SPF is merged, not duplicated.** A `v=spf1` TXT record at a name that already has one is merged into the existing record: your mechanisms are added, and the customer's `all` policy is kept. Two SPF records on one name would break mail. Send `replace: true` only if you really mean to overwrite the customer's SPF.
- **Porkbun parking or URL forwarding** at a name you add an A, AAAA or ALIAS record to is removed.

Example payload, before signing:

```json
{
  "iss": "your-client-id",
  "domain": "example.com",
  "service": "Google Workspace",
  "records": [
    { "type": "MX",  "host": "@", "content": "smtp.google.com", "prio": 1, "replace": true },
    { "type": "TXT", "host": "@", "content": "v=spf1 include:_spf.google.com ~all" },
    { "type": "TXT", "host": "@", "content": "google-site-verification=abc123" }
  ],
  "redirect_uri": "https://app.example-service.com/porkbun/callback",
  "state": "a8f3c2e1",
  "iat": 1790000000,
  "exp": 1790001800
}
```

Signing in Node.js:

```js
import jwt from "jsonwebtoken";

const now = Math.floor(Date.now() / 1000);
const request = jwt.sign(
  { iss: CLIENT_ID, domain, records, redirect_uri: CALLBACK, state, iat: now, exp: now + 1800 },
  CLIENT_SECRET,
  { algorithm: "HS256" }
);
res.redirect(`https://porkbun.com/dnsconnect/authorize?request=${request}`);
```

In Python:

```python
import jwt, time  # PyJWT

now = int(time.time())
request = jwt.encode(
    {"iss": CLIENT_ID, "domain": domain, "records": records, "redirect_uri": CALLBACK,
     "state": state, "iat": now, "exp": now + 1800},
    CLIENT_SECRET, algorithm="HS256")
redirect_to = f"https://porkbun.com/dnsconnect/authorize?request={request}"
```

Put the records in the JWT only, never as separate query parameters. Only `alg: HS256` is accepted.

## 3. Handle the callback

After approving, the customer clicks **Continue** on Porkbun's confirmation page. After denying, they're sent back straight away. Either way they arrive at your `redirect_uri` with these parameters:

| `status` | Meaning |
|----------|---------|
| `applied` | Approved, and every change was made |
| `partial` | Approved, but some changes failed. The customer saw which ones and was offered an undo |
| `denied` | The customer chose Deny (`error=access_denied`) |
| `error` | Nothing was changed. `error` is `domain_not_in_account` (the domain isn't in the Porkbun account they logged into), `domain_not_active`, or `invalid_records` (Porkbun couldn't accept a record; the customer was shown why) |

`state` is echoed back if you sent it. Check that it matches.

An `applied` status means the records are saved at Porkbun. If the domain uses other nameservers, the customer is warned that the change won't take effect. You should still check the records resolve (for example with a DNS lookup) before telling the customer the setup is complete.

Some requests never come back to you. A request that fails its signature check, has expired, or names an unregistered `redirect_uri` shows the customer an error on Porkbun and doesn't redirect, so a forged link can't use Porkbun to bounce someone to an arbitrary site.

## 4. Things to know

- **One use per request.** Once approved, the same JWT can't be applied again. Build a fresh one for each attempt.
- **Ask only for what you need.** Customers see every record, and they see warnings for broad changes. A request that moves mail when your service is a website builder will look wrong to them, and to Porkbun's review.
- **Ownership tokens are called out by name.** A Google, Microsoft or similar verification token is shown as "Proves ownership of *domain* to *service*". Include one only if that is the service being set up.
- **Undo is the customer's.** The customer can revert from the emailed link for 30 days. If your service stops working after that, the records were probably undone, so re-check them.
- **Security.** HMAC-signed requests, exact-match redirect URIs, the customer's own login with 2FA, per-render CSRF protection on the approval form, single-use requests, and a restore point before every change.


---

## More

- Guides (how-tos): https://porkbun.com/llms/guides
- Topic index: https://porkbun.com/llms
- Full reference (one file): https://porkbun.com/llms-full.txt
- OpenAPI spec (full schemas): https://porkbun.com/api/json/v3/spec
- Short overview: https://porkbun.com/llms.txt
- Official MCP server: https://porkbun.com/mcp (`npx -y @porkbunllc/mcp-server`)
- Create API keys: https://porkbun.com/account/api
