Skip to main content

UC-028 — Compliance and Opt-out (GDPR)

FieldValue
IDUC-028
GoalManage opt-out requests and contact deletion in GDPR compliance
ChannelAll (SMS, RCS, WhatsApp)
Complexity⭐⭐ Intermediate
Estimated time15 minutes
APIs involvedGET /api/partner-gateway/v1/contacts, DELETE /api/partner-gateway/v1/contacts, DELETE /api/partner-gateway/v1/contacts/list/contacts

Real-world scenarios​

  • FashionOutlet — GDPR Art. 17 request: A customer sends an email requesting the complete deletion of their data. The operator must remove them from all lists and delete the contact within 30 days.
  • TelcoMobile — Opt-out management: A user replies "STOP" to a promotional SMS. The system automatically removes them from marketing lists and adds them to the exclusion list.
  • ClinicaSalute — Unreachable contact cleanup: After a campaign, the marketing team removes all contacts with permanent ERROR status to keep lists clean and reduce costs.

Prerequisites​

Before you begin, make sure you have:

Test without costs

Add "simulation": true in the request body to validate the flow without actually sending messages and without consuming credit.

Opt-out and deletion flow​

The diagram illustrates the complete opt-out request handling flow: contact search, list removal, permanent deletion and confirmation.

Step 1 — Search for the contact by phone number​

When you receive an opt-out request, search for the contact in the system:

curl -X GET "https://api.qlara.ai/api/partner-gateway/v1/contacts?phoneNumber=%2B393471234567" \
-H "X-Api-Key: YOUR_API_KEY"

Response — Contact found​

{
"data": [
{
"id": 20481,
"fullName": "Marco Bianchi",
"firstName": "Marco",
"lastName": "Bianchi",
"phoneNumbers": ["+393471234567"],
"mobilePhoneNumber": "+393471234567",
"listIds": [1201, 1202, 1203]
}
],
"page": 0,
"limit": 10,
"totalCount": 1,
"totalPages": 1
}
Note the id and listIds

The contact's id field and its listIds will be needed for the following steps. A single call in Step 2 removes the contact from all of those lists; to see their names, call GET /contacts/list?contactIds=20481.

Step 2 — Remove the contact from all lists​

Remove the contact from all its lists in one call, passing the listIds from Step 1:

# Remove from "Clienti attivi", "Newsletter marketing" and "Promo stagionali"
curl -X DELETE "https://api.qlara.ai/api/partner-gateway/v1/contacts/list/contacts" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"listIds": [1201, 1202, 1203],
"contactIds": [20481]
}'

Response — Removed from lists​

true

There is no need to repeat the call for each list: all the lists in listIds are covered at once.

Behind the scenes — Why remove from lists before deleting
  1. Referential integrity: Removing the contact from lists before deletion ensures no orphan references remain. Some scheduled campaigns might still reference the lists.
  2. Audit trail: Explicit removal from each list generates trackable events in the log, useful for demonstrating GDPR compliance.
  3. Active campaigns: If a campaign is in the sending phase and uses one of these lists, preventive removal stops the contact from receiving further messages before complete deletion.
  4. GDPR timelines: The European Regulation (Art. 17) allows 30 days to complete the deletion. However, best practice is to immediately block sending (list removal) and proceed with deletion as soon as possible.

Step 3 — Delete the contact​

After removing the contact from all lists, proceed with permanent deletion:

curl -X DELETE "https://api.qlara.ai/api/partner-gateway/v1/contacts?ids=20481" \
-H "X-Api-Key: YOUR_API_KEY"

Response — Contact deleted​

true
Permanent deletion

Contact deletion is irreversible. All associated data (name, email, phone, list history) will be permanently removed. Make sure you have completed the necessary audit and backup operations before proceeding. Always pass the contact in ids: the notIds parameter works the other way round and deletes every contact in the account except the listed ones.

Step 4 — Verify the deletion​

Confirm that the contact no longer exists in the system:

curl -X GET "https://api.qlara.ai/api/partner-gateway/v1/contacts?phoneNumber=%2B393471234567" \
-H "X-Api-Key: YOUR_API_KEY"

Response — No results​

{
"data": [],
"page": 0,
"limit": 10,
"totalCount": 0,
"totalPages": 0
}

Expected result​

StepActionResult
1GET /contacts?phoneNumber=...Contact found with its listIds
2DELETE /contacts/list/contactsContact removed from all lists
3DELETE /contacts?ids={id}Contact permanently deleted
4GET /contacts?phoneNumber=...totalCount: 0, deletion confirmed

Complete end-to-end example​

Here is the full FashionOutlet scenario for a GDPR Art. 17 request:

#!/bin/bash
# Complete GDPR opt-out script for FashionOutlet

API_KEY="YOUR_API_KEY"
BASE_URL="https://api.qlara.ai/api/partner-gateway/v1"
PHONE="+393471234567"
PHONE_ENCODED="%2B393471234567"

echo "=== GDPR Opt-out Request ==="
echo "Request date: $(date -Iseconds)"
echo "Number: ${PHONE}"

# 1. Search for the contact
CONTACT=$(curl -s -X GET "${BASE_URL}/contacts?phoneNumber=${PHONE_ENCODED}" \
-H "X-Api-Key: ${API_KEY}")

CONTACT_ID=$(echo "$CONTACT" | jq -r '.data[0].id')
TOTAL=$(echo "$CONTACT" | jq -r '.totalCount')

if [[ "$TOTAL" == "0" || "$CONTACT_ID" == "null" ]]; then
echo "Contact not found. No action needed."
exit 0
fi

echo "Contact found: ${CONTACT_ID}"

# 2. Remove the contact from all its lists in one call
LIST_IDS=$(echo "$CONTACT" | jq -c '.data[0].listIds // []')

if [[ "$LIST_IDS" != "[]" ]]; then
echo "Removing from lists: ${LIST_IDS}"
curl -s -X DELETE "${BASE_URL}/contacts/list/contacts" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{"listIds": '"${LIST_IDS}"', "contactIds": ['"${CONTACT_ID}"']}' > /dev/null
fi

# 3. Delete the contact
echo "Deleting contact: ${CONTACT_ID}"
curl -s -X DELETE "${BASE_URL}/contacts?ids=${CONTACT_ID}" \
-H "X-Api-Key: ${API_KEY}" > /dev/null

# 4. Verify
VERIFY=$(curl -s -X GET "${BASE_URL}/contacts?phoneNumber=${PHONE_ENCODED}" \
-H "X-Api-Key: ${API_KEY}" | jq -r '.totalCount')

if [[ "$VERIFY" == "0" ]]; then
echo "Deletion confirmed. GDPR request completed."
else
echo "WARNING: contact still present. Manual verification needed."
fi

Variants​

Marketing-only opt-out (keep contact)​

If the user only wants to stop receiving promotional messages, remove them only from marketing lists while keeping the contact in the system for transactional communications.

Bulk cleanup of unreachable contacts​

After a campaign, batch-remove contacts with permanent errors by passing an array of contactIds, with the listIds to clean, to DELETE /contacts/list/contacts.

Common errors​

404 Not Found — Contact does not exist​

{ "status": "fail", "data": { "error": "Contact not found" } }

Solution: The contact may have already been deleted. Verify with GET /contacts before attempting deletion.

409 Conflict — Contact in use by active campaign​

{ "status": "fail", "data": { "error": "Contact is referenced by an active campaign" } }

Solution: Wait for the campaign to complete or remove the contact from the involved lists first.

Next steps​

References​