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:

:::tip 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://lora-api.agiletelecom.com/api/partner-gateway/v1/contacts?phone=%2B393471234567" \
-H "X-Api-Key: YOUR_API_KEY"

Response — Contact found

{
"content": [{
"id": "cnt_f47ac10b-58cc-4372-a567-0e02b2c3d479",
"phone": "+393471234567",
"firstName": "Marco",
"lastName": "Bianchi",
"lists": [
{ "id": "lst_001", "name": "Clienti attivi" },
{ "id": "lst_002", "name": "Newsletter marketing" },
{ "id": "lst_003", "name": "Promo stagionali" }
]
}],
"totalElements": 1
}

:::tip Note the contactId and lists The contact's id field and the list id values will be needed for the following steps. If the contact is present in multiple lists, you must remove them from each one. :::

Step 2 — Remove the contact from all lists

For each list the contact belongs to, remove them:

# Remove from "Clienti attivi"
curl -X DELETE "https://lora-api.agiletelecom.com/api/partner-gateway/v1/contacts/list/lst_001/contacts" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contactIds": ["cnt_f47ac10b-58cc-4372-a567-0e02b2c3d479"]
}'

Response — Removed from list

{
"removed": 1,
"listId": "lst_001",
"listName": "Clienti attivi"
}

Repeat for all lists (lst_002, lst_003, etc.) with the same pattern.

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://lora-api.agiletelecom.com/api/partner-gateway/v1/contacts/cnt_f47ac10b-58cc-4372-a567-0e02b2c3d479" \
-H "X-Api-Key: YOUR_API_KEY"

Response — Contact deleted

{
"deleted": true,
"contactId": "cnt_f47ac10b-58cc-4372-a567-0e02b2c3d479",
"deletedAt": "2026-04-09T16:45:00+02:00"
}

:::danger 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. :::

Step 4 — Verify the deletion

Confirm that the contact no longer exists in the system:

curl -X GET "https://lora-api.agiletelecom.com/api/partner-gateway/v1/contacts?phone=%2B393471234567" \
-H "X-Api-Key: YOUR_API_KEY"

Response — No results

{
"content": [],
"totalElements": 0,
"page": 0,
"size": 20
}

Expected result

StepActionResult
1GET /contacts?phone=...Contact found with list associations
2DELETE /contacts/list/{id}/contactsContact removed from all lists
3DELETE /contacts/{id}Contact permanently deleted
4GET /contacts?phone=...totalElements: 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://lora-api.agiletelecom.com/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?phone=${PHONE_ENCODED}" \
-H "X-Api-Key: ${API_KEY}")

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

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

echo "Contact found: ${CONTACT_ID}"

# 2. Retrieve associated lists
LISTS=$(echo "$CONTACT" | jq -r '.content[0].lists[].id')

for LIST_ID in $LISTS; do
echo "Removing from list: ${LIST_ID}"
curl -s -X DELETE "${BASE_URL}/contacts/list/${LIST_ID}/contacts" \
-H "X-Api-Key: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{"contactIds": ["'"${CONTACT_ID}"'"]}' > /dev/null
done

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

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

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 to DELETE /contacts/list/{listId}/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