Docs

Physical address verification

Confirm that a person receives mail at the address they gave you, with a letter that carries a code and your brand.

Physical address verification confirms that a person receives mail at the address they gave you. CartaFlow mails a letter with a 6-digit code to that address. When the person confirms the code, the address is verified: only someone who received the letter knows the code.

For example, a fintech gets an account application and asks to verify the address on it. When the letter arrives, the customer sees the fintech's logo, scans the QR code and confirms. The account now has a verified address. In Brazil this is a practical way to prove residence when utility bills and other documents are easy to fake or borrow.

The letter carries your brand

The verification letter goes out in your company's name. Neither the letter nor the confirmation page shows the CartaFlow name or logo. For now, the page lives on the verifica.cartaflow.com.br domain, at an address with your account id (the link printed on the letter and the one the QR code opens); pages on your company's own domain may come later. The page and the CartaFlow letter template use your logo and your company's trade name ("nome fantasia"), or its registered name ("razão social") when there is no trade name. Set both in the dashboard, under Configurações > Empresa (Settings > Company), before your first verification. With your own PDF, the letter carries your document's layout and brand.

The letter can be made in two ways:

WayWhen to use itPrice
CartaFlow letter template (default)You want a ready-made letter, with your logo and your texts.One base mailing on your plan.
Your own PDFYou already have a layout, such as a Word document, and only want to add the code.That of a letter with the same options.

Either way, the letter carries the 6-digit code and, with method: hosted_page, can carry the QR code and the address of the confirmation page. The CartaFlow letter template is in Portuguese, since it goes to people in Brazil.

Three ways to use it

OptionHow the person confirmsWhat you build
DashboardOn the page with your brand, through the QR code.Nothing. You request verifications in the dashboard and follow the results there.
API with method: hosted_pageThey scan the QR code and confirm on a page with your brand, hosted by CartaFlow.The call that creates the verification and a webhook to learn the result.
API with method: codeThey type the code into your website or app.The screen where the person types the code and the call that checks it.

With hosted_page, the page URL is in hosted_page_url. You can also email that link or show it in your app. After confirming, the page shows a button that takes the person to return_url, if you sent one.

The source field says where each verification was created: api or dashboard. It does not depend on how the person confirms, which is in confirmation. For example, a verification created in the dashboard is confirmed on the page, and one created through the API with method: hosted_page can be confirmed on the page or checked by your server.

Create a verification

Only name, postal_code and number are required in the address. The rest is filled in from the postal code when you leave it out, as with letters. See Addresses in Brazil.

curl -X POST "https://api.cartaflow.com.br/v1/verifications" \
  -H "Authorization: Bearer $CARTAFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: verification-customer-1842" \
  -d '{
    "to": { "name": "Destinatário Exemplo", "postal_code": "01001-000", "number": "128", "complement": "Ap. 52" },
    "method": "hosted_page",
    "return_url": "https://www.exemplo.com.br/conta",
    "reference": "customer-1842"
  }'

The response has status received, source: "api" and attempts_remaining set to 5. Without letter or document, the letter comes from the CartaFlow letter template, with the texts of your company's default letter. With method: code, replace return_url with instructions, for example "Abra o app, vá em Perfil > Endereço e digite o código." (written in Portuguese for the recipient). If you leave instructions out, we print a default line with your company name.

Nobody sees the code, not even you

Neither the API nor the dashboard shows the code, to anyone at your company. It only exists on the letter CartaFlow prints, and that is what proves the address. preview_url holds the letter as it will be printed, address included, but with the code hidden: it shows 000 000 in its place, and a box where the QR code will be printed.

CartaFlow letter template

Without document, CartaFlow lays out the letter: one A4 sheet in black and white with your logo, your company name, the address in the envelope window, a title, a text, the code and a footer. With method: hosted_page, it also carries the QR code, which opens the page with the code already in it, and the page address, for people who prefer to type.

What gets printed comes from three layers, in this order:

  1. The CartaFlow default.
  2. Your company's default letter, saved in the dashboard. It applies to every verification, created through the API or the dashboard.
  3. The letter field of the request, for this verification only.

Each field you leave out of letter comes from the layer before it.

letter fieldWhat it isCartaFlow default (in Portuguese)
titleThe title, in large type. Up to 80 characters.Confirme o seu endereço
bodyThe text before the code. Up to 1,200 characters.{{empresa}} pediu a confirmação de que você recebe correspondência neste endereço. Use o código abaixo para confirmar.
footerThe text at the foot of the page. Up to 300 characters.O código vale por 60 dias depois da postagem e só funciona para este endereço. Não compartilhe este código com ninguém.
include_qr_codePrints the QR code that opens the page with the code already in it.true
include_page_urlPrints the address of the confirmation page, for the person to open in a browser and type the code.true

The texts can use two markers: {{empresa}} ("company"), replaced with your company name, and {{nome}} ("name"), replaced with the recipient's name (to.name). Any other marker in braces gives a 422 with invalid_value. Write the texts in Portuguese: the fixed parts of the letter, such as "Seu código" ("Your code"), are in Portuguese.

Request body with a customized template
{
  "to": { "name": "Destinatário Exemplo", "postal_code": "01001-000", "number": "128" },
  "method": "hosted_page",
  "letter": {
    "title": "Confirme seu endereço, {{nome}}",
    "body": "{{empresa}} precisa confirmar o endereço do seu cadastro. Use o código abaixo.",
    "include_page_url": false
  }
}
  • With method: code, the QR code and the page address are never printed, even with include_qr_code or include_page_url set to true. The instructions text goes in their place.
  • The template always uses one sheet in black and white. color, double_sided and address_placement only apply to your own PDF.
  • The response holds what was used in document, with type: "template" and the texts still carrying their markers.

Your own PDF

Send your PDF in document, as for a letter: type: url, type: upload or type: base64. This endpoint only takes JSON: to send the file itself, use the two-step upload. The PDF follows the rules of a letter: A4 portrait, up to 6 sheets, selectable text and a blank address area, or address_placement: extra_page. See Document and address area.

Type the markers CartaFlow fills in into the document:

MarkerRequiredWhat CartaFlow prints in its place
{{OTP_CODE}}Yes. Without it, the API answers 422 with otp_code_missing.The code, as 123 456, in the marker's font, size and color.
{{VERIFICATION_URL}}No. Only with method: hosted_page.The address of the confirmation page, the same as hosted_page_url, without the code.
{{QR_CODE}}No. Only with method: hosted_page.A 25 x 25 mm QR code that opens the page with the code already in it. Its top-left corner sits where the marker starts.
  • Type the markers in capitals and with the braces, as in the table.
  • Leave room after {{OTP_CODE}} for the whole code and, from {{QR_CODE}}, a blank square of 25 mm to the right and down.
  • With method: code, {{VERIFICATION_URL}} and {{QR_CODE}} give a 422 with invalid_value. Write in the document where the person types the code.
  • Other markers, such as {{nome}}, are regular variables: send their values in document.variables. Do not send values for the reserved markers: the API answers variable_not_found.
  • color, double_sided and address_placement work as for a letter and are part of the price.
  • instructions is not printed on your own PDF.
Request body with your own PDF, sent beforehand with POST /v1/uploads
{
  "to": { "name": "Destinatário Exemplo", "postal_code": "01001-000", "number": "128" },
  "method": "hosted_page",
  "document": {
    "type": "upload",
    "upload_id": "upl_6Np3Vx8Rk2Tq",
    "variables": { "nome": "Destinatário Exemplo" }
  },
  "color": true,
  "reference": "cliente-2210"
}

The response holds in document the way the PDF was sent (upload, url or base64) and the variables you sent.

Check the letter right after creating it

Open preview_url as soon as the verification is created. While it is received, you can cancel it and the amount goes back to the balance.

Statuses

  1. Receivedreceived
    • Canceledcanceled
  2. In productionin_production
    • Canceledcanceled
    • Revokedrevoked
  3. Awaiting codeawaiting_code
    • Failedfailed
    • Expiredexpired
    • Revokedrevoked
  4. Verifiedverified
    • Revokedrevoked
StatusWhat it meansFinalCancelable
receivedReceivedCartaFlow received the request, charged the price to your balance and put the letter with the code in the production queue. If you cancel now, nothing is printed and the amount goes back to the balance.NoYes
in_productionIn productionThe letter with the code is being printed and enveloped. Canceling now revokes the verification (revoked), with no refund.NoYes
awaiting_codeAwaiting codeThe letter was posted. Now we wait for the person to confirm the code. They have 60 days from posting (see expires_at). Canceling now revokes the verification, with no refund.NoYes
verifiedVerifiedThe person confirmed the right code. They receive mail at the address on file. confirmation says how and when. You can still revoke the verification, which moves it to revoked.NoYes
failedFailed5 wrong codes were entered. The verification was locked to stop guessing. Create a new verification to try again.YesNo
expiredExpired60 days went by after posting without the person confirming the code.YesNo
canceledCanceledThe verification was canceled before posting and the amount went back to your balance. Either you canceled it while it was received, or CartaFlow canceled it because the letter could not be produced.YesNo
revokedRevokedYou canceled the verification after production started: in production, awaiting the code or already verified. The amount does not go back to the balance, because production already started, and a letter that was in production is still posted. The code stops working, on the page and in the API, and the verification never shows as verified again.YesNo

The code can only be confirmed while the status is awaiting_code, after posting. The person has 60 days from posting to confirm. The exact deadline is in expires_at. After it, the verification moves to expired.

verified is not the end: if you revoke the verification, it moves to revoked. Treat an address as verified only while the status is verified.

Check a code typed in your app

With method: code, the person types the code into your website or app, and your server checks it with POST /v1/verifications/{verification_id}/check. Always make this call from your server, never from the browser or the app.

curl -X POST "https://api.cartaflow.com.br/v1/verifications/ver_8Wn2Rt6Yk4Js/check" \
  -H "Authorization: Bearer $CARTAFLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "482917" }'

A wrong code is not an API error. The response is always 200, with correct: false, and attempts_remaining goes down. After 5 wrong codes, counting those typed on the hosted page and through the API, the verification moves to failed. To try again, create a new verification.

Error responses only happen when a check makes no sense at that moment:

SituationResponse
The letter has not been posted yet, or the verification is already finished (verified, failed, expired, canceled, revoked).409 with verification_not_awaiting_code. The message gives the current status.
The code does not have 6 digits.422 with invalid_format on code. This attempt does not count.

The screen where the person types the code

A few choices make confirming easier:

  • Use a numeric field (inputmode="numeric" and autocomplete="one-time-code") for 6 digits.
  • Accept the code with spaces or dashes and strip them before sending.
  • After a wrong code, say how many attempts are left.
  • When the verification is failed or expired, explain what happened and offer to send a new letter.

Suggested messages:

SituationMessage
Right codeAddress confirmed. Thank you.
Wrong codeThis code does not match. You have 3 attempts left.
Last attemptThis code does not match. You have 1 attempt left.
No attempts left (failed)We could not confirm the address with this code. Request a new verification letter.
Deadline passed (expired)The time to confirm this code is over. Request a new verification letter.
Not posted yet (409)The letter with the code is still on its way. Try again once it arrives.
Canceled or revoked (409)This verification was canceled. If you expected to confirm your address, contact us.

If your users are in Brazil, write these messages in Portuguese. The Portuguese version of this page has the same list.

How the confirmation happened

Once the right code is confirmed, the verification has a confirmation. Before that, confirmation is null.

confirmation of a verification confirmed through the QR code
"confirmation": {
  "method": "hosted_page",
  "hosted_page_entry": "qr_code",
  "confirmed_at": "2026-10-21T19:32:00Z",
  "ip": "198.51.100.23",
  "location": { "city": "São Paulo", "region": "SP", "country": "BR", "postal_code": "01000", "latitude": -23.5475, "longitude": -46.6361 },
  "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
  "device": { "type": "mobile", "os": { "name": "iOS", "version": "17.5" }, "browser": { "name": "Safari", "version": "17.5" } },
  "client": { "language": "pt-BR", "accept_language": "pt-BR,pt;q=0.9", "timezone": "America/Sao_Paulo", "screen": { "width": 390, "height": 844, "pixel_ratio": 3 }, "time_on_page_ms": 8100 },
  "precise_location": {
    "latitude": -23.5489, "longitude": -46.6388, "accuracy_m": 14.5, "distance_m": 606,
    "shared_at": "2026-10-21T19:32:40Z", "ip": "198.51.100.23",
    "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1",
    "address_coordinates": { "latitude": -23.5503898, "longitude": -46.633081 }
  }
}
FieldWhat it is
methodhosted_page, when the person confirmed on the page, or api, when your server checked the code with /check.
hosted_page_entryOn the page: qr_code, when the person scanned the QR code, or typed, when they typed the code. null with method: api.
confirmed_atWhen the right code was confirmed.
ipThe IP address the confirmation came from. On the page, it is the person's device. Through the API, it is your server, not the person.
locationCity, region (in Brazil, the state, or UF), country, postal code, latitude and longitude, estimated from the IP address. Only on the page; null through the API.
user_agentThe user agent of the browser that confirmed. Through the API, your server's.
deviceDevice type (mobile, tablet or desktop), operating system and browser, with their versions, read from user_agent.
clientThe browser's language, the Accept-Language header, the time zone, the screen and how long passed between opening the page and sending the code. Through the API, only accept_language, when your server sends it.
precise_locationThe device's location, only when the person chooses to share it after confirming (see Optional location), with the distance to the address's CEP.

Every field that could not be recorded is null. Confirmations recorded before 2026-10-14 have null in user_agent, device, client and precise_location.

IP address, location and device are signals

They are recorded when available and can be null. location comes from the network, not from GPS: a mobile network, a VPN or a proxy can point to another city or country. device and client come from what the browser reports. Use all of it as more signals in a fraud review, never as proof of the address. The proof is the code, which only arrives in the letter.

A verification revoked after being confirmed keeps confirmation and status_transitions.verified_at for the record, but its status is revoked.

The confirmation page

With method: hosted_page, CartaFlow hosts the page where the person confirms, with your logo and your company name, and nothing of CartaFlow: no name, logo or colors. The address is https://verifica.cartaflow.com.br/{your account id}/{page token}, the same as hosted_page_url. Use hosted_page_url as it came, instead of building the address yourself.

What the person sees:

  • Your logo and your company's trade name (the registered name when there is no trade name).
  • Through the QR code, the page opens with the code already filled in: the person taps "Confirmar endereço" (Confirm address) once. Opening the link never confirms on its own.
  • Through the printed address, or through hosted_page_url sent by you, the person types the 6 digits.
  • The result: address confirmed, or wrong code with the attempts left.
  • After confirming, a button to return_url, if you sent one, and the option to share their location.
  • A short notice about what is recorded to prove the confirmation and who is responsible for the data.
  • Your company's contact, when set (below).

The page is in Portuguese, works on phones and computers, is not indexed by search engines and has no analytics or third-party scripts.

When the verification no longer waits for a code, the page accepts no codes and explains why, naming your company and its contact:

StatusWhat the page says
received, in_productionThe letter with the code has not been posted yet.
verifiedThe address was already confirmed.
failedThe code was locked after 5 wrong attempts. The person should contact your company to get a new letter.
expiredThe time to confirm is over. The person should contact your company to get a new code.
canceled, revokedYour company canceled the verification. If the person expected to confirm their address, they should contact your company.

Get your support team ready for these contacts.

Your contact on the page

Under Configurações > Empresa (Settings > Company), you can enter your company's website, e-mail, phone and privacy policy address. All of them are optional. The page shows whatever is filled in, also in the messages that ask the person to contact your company, and the privacy policy link goes in the notice about the data.

What the page records

On every attempt, right or wrong, the page records: date and time, IP address, the IP's approximate location, user agent, device, operating system and browser, language, time zone, screen, how long the person stayed on the page, and whether the code came from the QR code or was typed. All of it shows in the dashboard, on the verification's detail, and the confirmation shows in the API, in confirmation. For now, none of these records can be turned off.

Against automated attempts, besides the limit of 5 wrong codes per verification, the page limits attempts and requests per IP address.

Optional location

After confirming the code, the page offers "Compartilhar minha localização" (Share my location). If the person taps it and the browser has permission, the page sends the device's position, with the accuracy the device reports. It is never asked for before the code, never required, and only accepted once, up to 30 minutes after the confirmation.

The location goes in confirmation.precise_location, with the distance in meters to the coordinates of the address's CEP (distance_m). A CEP marks a street or a stretch of it, not the house number, so a distance of a few hundred meters is normal. distance_m is null when we do not have that CEP's coordinates.

Follow it with webhooks

The key event is verification.verified. With method: hosted_page, it is how your system learns about the confirmation, and data.confirmation says how it happened. Also handle verification.revoked: a revoked verification no longer counts as verified, even if it was confirmed before. In events, preview_url is null: retrieve the verification to get the link. See Webhooks.

EventWhen it is sent
verification.receivedA verification was created, through the API or the dashboard (see source), and the letter with the code is in the production queue.
verification.in_productionThe letter with the code is being printed. From here on, canceling revokes the verification, with no refund.
verification.awaiting_codeThe letter with the code was posted. The person can confirm once it arrives.
verification.verifiedThe person confirmed the right code. The address is verified.
verification.failed5 wrong codes were entered and the verification was locked.
verification.expiredThe person did not confirm the code within 60 days of posting.
verification.canceledThe verification was canceled before posting, by you or by CartaFlow, and the amount went back to the balance.
verification.revokedYou canceled the verification after production started. The code stopped working and the amount does not go back to the balance. If it was verified, treat the address as not verified.

Price

With the CartaFlow letter template, each verification costs the same as one base mailing on your plan: a single base line in price. With your own PDF, the price is that of a letter with the same options: base, plus additional_sheets and color when they apply. A verification always goes as a simple letter ("carta simples"), with no Correios services. The amount is debited from your balance at creation. See Pricing and balance.

Cancel or revoke

POST /v1/verifications/{verification_id}/cancel cancels or revokes the verification, depending on its status. The dashboard does the same, with the same rules.

StatusResultAmount
receivedcanceled: nothing is printed.Goes back to the balance.
in_production, awaiting_code or verifiedrevoked: the code stops working, on the page and in the API, and the verification never shows as verified again.Does not go back.
canceled or revokedNothing changes: the response carries the same verification.
failed or expired409 with verification_not_cancelable.

Revoke when the address should no longer count as verified, for example when the account was closed or you suspect fraud. Revoking does not stop delivery: a letter that was already in production is still posted, and whoever tries to confirm the code sees the notice telling them to contact your company.

A verification can also move to canceled without a request from you, when the letter cannot be produced. In that case the amount goes back to the balance automatically.

Only verify an address that the person gave you, and tell them a letter with a code is on its way. The letter is a message from your company to your customer.

Your company is responsible for the data recorded on confirmation (the data controller, under Brazil's LGPD), and the page tells the person so. Enter your privacy policy address under Configurações > Empresa (Settings > Company), so it shows on the page.

On this page