UC-028 — Compliance and Opt-out (GDPR)
| Field | Value |
|---|---|
| ID | UC-028 |
| Goal | Manage opt-out requests and contact deletion in GDPR compliance |
| Channel | All (SMS, RCS, WhatsApp) |
| Complexity | ⭐⭐ Intermediate |
| Estimated time | 15 minutes |
| APIs involved | GET /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:
- Active API Key → How to get one
- Sufficient credit → Check in the Qlara Dashboard
- Contact ID or phone number to search
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
}
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
- Referential integrity: Removing the contact from lists before deletion ensures no orphan references remain. Some scheduled campaigns might still reference the lists.
- Audit trail: Explicit removal from each list generates trackable events in the log, useful for demonstrating GDPR compliance.
- 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.
- 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
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
| Step | Action | Result |
|---|---|---|
| 1 | GET /contacts?phoneNumber=... | Contact found with its listIds |
| 2 | DELETE /contacts/list/contacts | Contact removed from all lists |
| 3 | DELETE /contacts?ids={id} | Contact permanently deleted |
| 4 | GET /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
- UC-007 — Manage Contacts and Lists: Learn more about complete contact management
- UC-011 — Export Delivery Reports: Export history for GDPR audit
- UC-006 — Bulk SMS Campaign: Manage campaigns with contact lists
- Authentication Guide: Details on API Key and permissions