How it worksFeaturesSolutionsGuides PricingFAQ Ask on WhatsApp
Help Customers & contacts

Importing your contacts

The file format, what happens to a bad row, and the consent record a contact needs before any promotion can be sent to them.

Updated 16 August 2026 8 min read

If you already have a customer list — in a spreadsheet, in your old billing software, in a notebook somebody typed up — you can bring it in as a file rather than adding people one at a time.

Where to find it: open Contacts in your dashboard, then Import contacts. You need admin access.

⚠️ Having someone’s number is not the same as being allowed to promote to them. Importing a list gets them into Kooday. It does not make them contactable for offers — that needs a record of when and how they agreed, and it is the second half of this page.

The file

A CSV or a JSON file, up to 5 MB. The columns are fixed:

ColumnRequiredNotes
phone_numberYesDigits, plus spaces, +, brackets and dashes. A ten-digit number is read as Indian and stored as +91…. A cell with letters in it, or with no digits, fails that row
nameYesCannot be blank
emailNoBlank is fine
is_activeNotrue/false, yes/no, 1/0. Blank means active
attributesNoAnything else you want to keep — see below
consent_sourceNoWhere their agreement to hear from you came from — see Consent
consent_atNoWhen. Ignored unless consent_source is filled in too

A minimal CSV that works:

phone_number,name,email,is_active,attributes
9000000001,Priya Menon,priya@example.com,true,
+91 90000 00002,Ravi Kumar,,true,"{""city"":""Kochi"",""plan"":""annual""}"

The attributes column

Everything your business knows about a customer that Kooday has no column for — their city, their vehicle, which branch they use, when their policy renews. It is stored exactly as you give it and never interpreted, so you can put in whatever is useful to you.

In a CSV it is one cell holding JSON, which means the quotes inside it have to be doubled, as in the example above. In a JSON file it is an ordinary nested object and much easier to write by hand. If your spreadsheet is fighting you over the quoting, export JSON instead.

An invalid attributes cell fails that one row and nothing else.

Rows are matched on the phone number

Importing the same file twice does not create duplicates. A phone number already in your contacts is updated; a new one is created. The number is tidied into a standard form first, so +91 90000 00001 and 9000000001 are the same person.

That makes the import safe to re-run, which is the usual way people use it: export from the old system, import, notice three columns are wrong, fix them, import again.

What you get back

Three figures — created, updated, skipped — and, if anything was skipped, a table of exactly which rows and why.

A bad row never stops the import. The good rows go in, the bad rows are listed, and you fix those and re-upload. The reasons are plain: phone_number is required, name is required, attributes is not valid JSON, invalid is_active value.

A file that cannot be read at all does not fail loudly — it comes back as a single skipped row reading could not parse file: …, with nothing imported. Same table, same place to look; there is just one line in it.

Before Kooday will send anyone a promotion, it needs a consent record on that contact: where their agreement to hear from you came from, and when.

This is not a checkbox you tick to unlock sending. It is a record of something you already have — the form they filled in, the box on your order page, the sheet in the shop.

⚠️ We record what you tell us. We do not check it, and it does not make you compliant with anything. Meta’s rules require you to hold the opt-in. Whether you do is a fact about your business that nothing in this software can see. What Kooday does is keep the record, and refuse to send to anyone who has none.

What happens without one

A contact with no consent record is not sent to. They stay in the audience count, they appear in the campaign report as no consent record, and no message goes out to them.

They are shown rather than quietly dropped on purpose. A campaign that silently messaged 300 of the 1,000 people you selected, with no explanation, teaches you nothing. One that says “700 had no consent record” tells you exactly what to go and fix.

They look like two ends of one switch and they are not.

A consent recordAn opt-out
Who it comes fromYou — “they agreed, here is where”The customer — “stop”
What it doesAllows promotionsStops them
Who can change itYouOnly the customer

A consent record never overrides an opt-out. Somebody who said stop stays stopped, whatever your list says about them, and re-importing a file cannot undo it. Nobody on your team, and nobody here, can put a customer back — see When a customer asks you to stop for why that is deliberate.

Once a contact has a consent source, campaigns can send to them. There are three ways to put one there, and every one of them asks you the same question first.

1. In the file. Add a consent_source column (and optionally consent_at). On the import screen, pick the source and tick the box that says your business holds the opt-in. A file that carries a consent_source column and arrives without that box ticked is refused whole — “this file carries a consent_source, so it needs an explicit attestation…” — and nothing is imported.

2. One contact at a time. The new-contact and edit-contact forms have the same source picker and the same box. Setting a source without ticking it is refused the same way.

3. For people already in your book. Select them on the Contacts list and choose Record consent…. Pick the source, the date if you know it, tick the box, and it applies to everyone selected — no re-import, no file. There is a limit on how many one claim may cover; a very large selection is refused rather than trimmed, because a claim you cannot stand behind is worse than a slow afternoon.

However you do it, we write down that you told us: who, when, how many contacts, and which sources were claimed. That is not us checking up on you. It is so that if anyone ever asks where a customer’s permission came from, the answer exists and is yours.

Two smaller rules worth knowing:

  • Leaving the column out never retracts anything. Re-importing the same list with the consent columns dropped — the ordinary name-and-email refresh — leaves the existing record alone. Removing one has to be a deliberate act, not a side effect of a spreadsheet export.
  • A date on its own is discarded. consent_at without consent_source is not a record of where an opt-in came from, and storing it would make the contact look provenanced everywhere the pair is shown.

What is worth deciding before you fill the column in, or have that conversation:

  • A source is a real answer, not a category — “the enquiry form on our website”, “the tick box at checkout”, “the register in the shop”.
  • One source can cover a whole import, if that is the truth. A list exported from your booking system where everyone booked through the same form has one source.
  • A number you bought, scraped, or copied out of a WhatsApp group has no source. That is not a gap to work around. It is the answer, and those contacts should stay out of your promotional campaigns.

Troubleshooting

Every row was skipped

Check the header line. Column names are matched exactly and in lower case — phone_number, name, email, is_active, attributes, consent_source, consent_at — so a spreadsheet that exported Phone Number or Mobile matches nothing, and every row fails for a missing phone_number and name.

”attributes is not valid JSON”

In a CSV, the whole JSON object goes in one cell and every quote inside it has to be doubled. Excel and Google Sheets both do this for you if you type the JSON into the cell and let them handle the export — problems usually come from a file edited by hand afterwards. Exporting JSON instead avoids the whole question.

The file was rejected outright

Only two things refuse a whole file: one over 5 MB, and one carrying a consent_source column uploaded without the attestation box ticked (below). Everything else is reported row by row.

A file that is not UTF-8 text, or a JSON file that is not a list of objects, is not refused — it comes back with one skipped row saying it could not be parsed, and nothing imported.

“This file carries a consent_source, so it needs an explicit attestation…” — your file claims consent and the box on the upload screen was not ticked. Tick it, pick the source, and upload again. If you are not in a position to make that statement, import the file without the consent_source and consent_at columns — the contacts land, simply without a consent record; see Recording consent.

Contacts imported but campaigns will not send to them

They have no consent record. See above. This is the intended behaviour, not a missing setting, and it is the one refusal with no override.

A second cause looks identical and is not the contacts’ fault: a campaign whose audience was a pasted list of phone numbers rather than a segment has no contacts behind it at all. Kooday refuses that campaign at creation rather than letting it run and send nothing — the way through is this page: import the numbers as contacts with their consent, then build the campaign from a segment.

An imported contact is missing from a segment

Segments are worked out from what a customer has done — bought something, been quoted, gone quiet on a deal. A freshly imported contact with no history matches most of them by not being in them yet. That is correct: a list of strangers is not a list of dormant customers.

Still stuck? We answer support mail the same working day.

Email support