An agency handoff fails when the client receives a spreadsheet with no source,
no limits, and no way to reproduce it. Zalo work adds another constraint: the
data belongs to the account the client connects, and the first connection needs
a QR approval.
The workflow below turns that constraint into a clear operating procedure. It
is built for agencies and automation teams supporting sales, CRM, community,
or data operations in Vietnam.
Disclosure: I built the Zalo Data Exporter used in this workflow. I may
earn revenue from Apify when the Actor is run through links in this article,
at no extra cost to you.
Start with the deliverable
Do not begin by asking for every available Zalo record. Pick the file the client
will use.
| Client job | Actor preset | Delivery |
|---|---|---|
| Reconcile a joined-group roster | groupRoster |
Groups, visible members, linked profiles |
| Prepare visible contacts for CRM | contactsAndProfiles |
Profile and relationship rows |
| Preserve accessible business chats | conversationBackup |
Conversation summaries and message rows |
| Build a bounded internal snapshot | completeSnapshot |
All visible record families |
For a new engagement, groupRoster is usually the easiest proof. One group and
100 visible members produce a result the client can check without launching a
large account-wide job.
Assign one alias to one Zalo account
The Actor saves an encrypted login under accountAlias. The alias is scoped to
the Apify user, but it still needs a simple naming rule inside an agency account.
Use names that identify the operational connection without exposing a phone
number or secret:
{
"accountAlias": "client-acme-vn",
"exportMode": "groupRoster",
"maxGroups": 1,
"maxMembersPerGroup": 100
}
If a second client connects another Zalo account, give it another alias. Do not
reuse an alias to switch accounts. Refresh the same alias only when the same
connected account needs a new login.
Put the QR step in the onboarding call
The first connection is not an unattended background task. Schedule it as part
of onboarding:
- Open the small starter run in Apify Console.
- Ask the account owner to scan the temporary QR in Zalo.
- Keep the run open until the Dataset appears.
- Confirm the account alias and the returned record types.
The QR expires quickly, so the account owner should be ready before the run
starts. The QR record is removed after success, rejection, or expiry. Later
runs can reuse the encrypted saved login while Zalo accepts the session.
Define limits in the statement of work
Record limits are part of the deliverable, not hidden technical settings. Write
them into the project scope.
Example group-roster scope:
{
"exportMode": "groupRoster",
"accountAlias": "client-acme-vn",
"maxGroups": 3,
"maxMembersPerGroup": 250
}
Example chat scope:
{
"exportMode": "conversationBackup",
"accountAlias": "client-acme-vn",
"messagesSince": "2026-07-01",
"maxConversations": 3,
"maxMessagesPerConversation": 200,
"includeMessageText": true,
"includeAttachmentMetadata": true
}
The limits control runtime, output volume, and the possible Actor charge. They
also make acceptance testing possible. A client can verify three requested
conversations more easily than an undefined request for the entire account.
Deliver typed views, not one flattened mystery file
The Dataset contains five possible recordType values:
profilegroupgroupMemberconversationmessage
Apify provides focused views for each family. Keep recordType in raw CSV or
JSON exports so the client can reconstruct the source model.
For spreadsheet delivery, a practical package is:
delivery/
groups.csv
group-members.csv
profiles.csv
run-summary.txt
The run summary should state the account alias, collection time, configured
limits, returned counts, and any partial conversation status. Never turn a
partial read into a silent success claim.
Connect the Dataset to the client stack
Developers can retrieve the result with ApifyClient:
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_API_TOKEN"])
run = client.actor("kazkn/zalo-member-profile-exporter").call(
run_input={
"exportMode": "groupRoster",
"accountAlias": "client-acme-vn",
"maxGroups": 3,
"maxMembersPerGroup": 250,
}
)
items = client.dataset(run["defaultDatasetId"]).list_items().items
From there, send group-member rows to a CRM staging table, save profiles to a
warehouse, or publish a controlled spreadsheet. The client remains responsible
for deciding which exported fields belong in each downstream system.
Set support expectations before the first run
Three cases deserve explicit wording in the handoff:
- A new or expired session needs another QR approval.
- The Actor returns only data visible to the connected account.
- Zalo can change its private web behavior, challenge a session, or stop a history read at a platform boundary.
The Actor does not recover deleted messages, discover hidden members, join
groups, or send messages. An agency should reject those requirements instead of
burying them in a generic scraping promise.
Agency delivery checklist
- One saved alias per connected Zalo account.
- One small proof run before increasing limits.
- A named preset and explicit record cap in the project scope.
- Separate Dataset views plus the run summary.
- A documented QR refresh contact on the client side.
- No Apify token, QR image, or session material in the delivery folder.
This setup gives the client a reproducible export and gives the agency a support
boundary it can explain before the first invoice.
Top comments (0)