Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions api/cohorts/sync_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@
)

_LIST_RESPONSE = inline_serializer(
"AmplitudeListResponse", {"listId": serializers.UUIDField()}
"AmplitudeListResponse",
{
"list_id": serializers.UUIDField(),
"response": inline_serializer(
"AmplitudeListResponseEnvelope", {"list_id": serializers.UUIDField()}
),
},
)

_MIXPANEL_RESPONSE = inline_serializer(
Expand Down Expand Up @@ -73,10 +79,15 @@ def create(self, request: Request) -> Response:
name=serializer.validated_data["name"],
source_type=CohortSourceType.AMPLITUDE,
)
# Amplitude's production sync worker reads the list ID from its
# documented default key, camelCase "listId", ignoring the response
# path configured in the Integration Portal.
return Response({"listId": str(cohort.uuid)})
# Amplitude reads the list ID from this body at the path configured
# in the Integration Portal, which their portal requires to start
# with "response." — but the two sides of Amplitude disagree on what
# that path is applied to. Their Testing tab wraps this body in a
# {"response": ...} envelope first, so it finds the flat copy; their
# production sync worker applies the same path to the raw body, so
# it needs the nested copy. Verified against both, 2026-08-28.
list_id = str(cohort.uuid)
return Response({"list_id": list_id, "response": {"list_id": list_id}})

@action(detail=True, methods=["POST"])
def add(self, request: Request, pk: str) -> Response:
Expand Down
10 changes: 6 additions & 4 deletions api/tests/unit/cohorts/test_sync_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def test_amplitude_create_list__valid_key__creates_amplitude_cohort(

# Then
assert response.status_code == status.HTTP_200_OK
cohort = Cohort.objects.get(uuid=response.json()["listId"])
cohort = Cohort.objects.get(uuid=response.json()["list_id"])
# Amplitude's Testing tab reads the flat copy, production the nested one.
assert response.json()["response"]["list_id"] == response.json()["list_id"]
assert cohort.environment == key.environment
assert cohort.source_type == CohortSourceType.AMPLITUDE
assert cohort.segment.name == "[Amplitude] Beta users: 1234"
Expand All @@ -74,7 +76,7 @@ def test_amplitude_create_list__postgres_environment__creates_cohort(

# Then
assert response.status_code == status.HTTP_200_OK
cohort = Cohort.objects.get(uuid=response.json()["listId"])
cohort = Cohort.objects.get(uuid=response.json()["list_id"])
assert cohort.environment == environment


Expand Down Expand Up @@ -350,7 +352,7 @@ def test_amplitude_create_list__valid_key__audits_and_queues_environment_update(
# Then - the audit record carries no user, names the source, and is the
# hook that rebuilds the environment document.
assert response.status_code == status.HTTP_200_OK
cohort = Cohort.objects.get(uuid=response.json()["listId"])
cohort = Cohort.objects.get(uuid=response.json()["list_id"])
audit_log = AuditLog.objects.get(related_object_id=cohort.segment_id)
assert audit_log.author is None
assert audit_log.master_api_key is None
Expand All @@ -377,7 +379,7 @@ def test_amplitude_create_list__valid_key__history_records_no_user(
# Then - a machine caller leaves no user on historical records; stamping
# one would fail, since the sync key is not a Flagsmith user.
assert response.status_code == status.HTTP_200_OK
cohort = Cohort.objects.get(uuid=response.json()["listId"])
cohort = Cohort.objects.get(uuid=response.json()["list_id"])
history_record = cohort.segment.history.get()
assert history_record.history_user is None
assert history_record.master_api_key is None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ Attributes:
### `cohorts.sync_webhook.rejected`

Logged at `warning` from:
- `api/cohorts/sync_views.py:217`
- `api/cohorts/sync_views.py:228`

Attributes:
- `action`
Expand Down
15 changes: 13 additions & 2 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18943,11 +18943,22 @@ components:
AmplitudeListResponse:
type: object
properties:
listId:
list_id:
type: string
format: uuid
response:
$ref: '#/components/schemas/AmplitudeListResponseEnvelope'
required:
- listId
- list_id
- response
AmplitudeListResponseEnvelope:
type: object
properties:
list_id:
type: string
format: uuid
required:
- list_id
AuditLogList:
type: object
properties:
Expand Down
Loading