Email consent
The endpoints that start an email double opt-in and confirm it from the link, with parameters, examples, responses and errors.
These endpoints start a double opt-in for an email address and confirm it. Paths are relative to https://console.dpconsent.com/consent/sdk/v1. The whole flow is in Email double opt-in.
Endpoints#
POST /email-consent#
Creates a PENDING record for an email address and returns the confirmation link that your server emails to the person.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| Secret | consent:write |
Accepted | consent.granted with the status PENDING for a new record, consent.updated for one still waiting |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
email |
email address, up to 255 | Yes | The address to confirm. |
formKey |
string, up to 60 | Yes | The form the person answered. |
decisions |
array, 1 or more | Yes | The answers to hold until the link is followed. Answer every purpose that is not always on. |
locale |
string, up to 10 | No | The language the form was shown in. |
returnUrl |
string, up to 500 | No | An absolute http or https address to send the person to after they confirm. |
Request
curl -X POST https://console.dpconsent.com/consent/sdk/v1/email-consent \
-H "Authorization: Bearer $PIEDAP_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: optin-7c41" \
-d '{
"email": "rohan.das@example.in",
"formKey": "newsletter",
"decisions": [{ "purposeKey": "newsletter", "granted": true }],
"locale": "en",
"returnUrl": "https://www.example.in/subscribed"
}'const pending = await client.startEmailConsent({
email: 'rohan.das@example.in',
formKey: 'newsletter',
decisions: [{ purposeKey: 'newsletter', granted: true }],
locale: 'en',
returnUrl: 'https://www.example.in/subscribed',
idempotencyKey: 'optin-7c41',
});from piedap_consent import Decision
pending = client.start_email_consent(
"rohan.das@example.in",
"newsletter",
[Decision("newsletter", True)],
locale="en",
return_url="https://www.example.in/subscribed",
idempotency_key="optin-7c41",
)Response
202:
{
"consentId": "cns_91b7d0c4a2e64f0f8d3c5a7b9e1f2c60",
"status": "PENDING",
"verificationUrl":
"https://console.dpconsent.com/consent/sdk/v1/email-consent/verify?token=…",
"expiresOn": "2026-09-26T10:12:03.000Z"
}| Field | Type | Description |
|---|---|---|
consentId |
string | The record that waits for confirmation. |
status |
string | Always PENDING. |
verificationUrl |
string | The link to email to the person. The API sends no email. |
expiresOn |
time | When the link stops working, 48 hours after it was made. |
Errors
| Status | Code | When |
|---|---|---|
403 |
EMAIL_CONSENT_SERVER_ONLY |
The call was made with a publishable key. |
403 |
PROPERTY_NOT_FOR_KEY |
The key is tied to a property that is no longer active. |
404 |
CONSENT_FORM_NOT_FOUND |
No form has this formKey. |
400 |
AMBIGUOUS_FORM_KEY |
The formKey exists on more than one property. Use a key tied to one property. |
400 |
INVALID_PURPOSE_DECISIONS |
A purpose was left unanswered, or an answer names a purpose the form does not offer. |
400 |
INVALID_PARTNER_DECISIONS |
A partners answer names a partner the version does not list, or the version offers no choices per partner. |
400 |
CONSENT_FORM_NOT_PUBLISHED |
The form has no published version. |
Also the errors every call can return.
GET /email-consent/verify#
Shows the person a page with a Confirm button. Nothing changes yet, so a mail scanner that follows the link confirms nothing.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| None. The token in the link is the credential | None | Ignored | None |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | The single-use token. It is already in verificationUrl. |
returnUrl |
string | No | Present in verificationUrl when you sent returnUrl. |
sig |
string | No | The signature that goes with returnUrl. Present in verificationUrl. |
Request
The person opens verificationUrl in a browser:
https://console.dpconsent.com/consent/sdk/v1/email-consent/verify?token=…Response
200 with an HTML page, in English.
Errors
None. A token that is not valid is refused when the person presses Confirm.
POST /email-consent/verify#
Confirms the consent. The Confirm button sends this request.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| None. The token in the link is the credential | None | Ignored | consent.updated |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | Query parameter. The single-use token from the link. |
returnUrl |
string | No | Query parameter. Where to send the person afterwards. |
sig |
string | No | Query parameter. The signature issued with returnUrl. Without a valid one, returnUrl is not followed. |
Request
curl -X POST \
"https://console.dpconsent.com/consent/sdk/v1/email-consent/verify?token=$TOKEN" \
-H "Accept: application/json"Response
| Case | Answer |
|---|---|
A signed returnUrl is present |
303 redirect to it, with consent=confirmed or consent=failed added to the query |
The request sends Accept: application/json |
200 with the consent record. A link that was already used answers 200 with consentId and "alreadyConfirmed": true. |
| Otherwise | 200 with a short thank you page, also when the link was already used |
Errors
| Status | Code | When |
|---|---|---|
400 |
EMAIL_CONSENT_TOKEN_EXPIRED |
The link is older than 48 hours. |
404 |
VERIFICATION_TOKEN_NOT_FOUND |
The token is not known. |
Every code is explained in Errors.
Related#
- Set up email double opt-in
- Hosted page links for unsubscribe links
consent.updated