Create a Cloudflare R2 bucket and API token, connect it to ReachCrew, and store WhatsApp call recordings and contact photos privately.
Cloudflare R2 is the file storage ReachCrew uses for WhatsApp call recordings and contact photos. You need it before you place recorded calls. Without it those two features are off.
What you need
A Cloudflare account with an R2 subscription. Cloudflare bills R2 usage to you directly, with a free allowance each month. Check R2 pricing for current terms.
ReachCrew stores two kinds of files in your bucket:
Call recordings, under calls/YYYY/MM/ (one file per call, Opus audio in a .webm or .ogg container).
Contact photos, under photos/people/.
Nothing in the bucket is public. The recorder extension uploads each recording with a presigned URL that ReachCrew creates for that one file, and it expires after 15 minutes. Playback in the app uses a presigned link that expires after 5 minutes. Contact photos are shown through a link valid for 1 hour. The Cloudflare key you save never leaves the server.
In the Cloudflare dashboard, open Storage & databases → R2 object storage and create a bucket (see Cloudflare’s guide). A bucket name uses only lowercase letters, numbers and hyphens, 3 to 63 characters, and does not start or end with a hyphen. Leave it private (the default). If R2 is not enabled yet, Cloudflare asks you to add an R2 subscription first.
2
Copy your Account ID
ReachCrew builds the storage address https://ACCOUNT_ID.r2.cloudflarestorage.com from it. In the dashboard, press Cmd/Ctrl + K, type Copy account ID and select the result. Other places are listed in Cloudflare’s guide.
3
Create an R2 API token
On the R2 object storage page, click Manage next to API Tokens (under Account Details). Click Create Account API token (or Create User API token). Under Permissions choose Object Read & Write, and scope the token to the bucket you created. Create it. Cloudflare shows an Access Key ID and a Secret Access Key. Copy both now: the secret is shown only once. Details are in Cloudflare’s token guide.
4
Open the Integrations page in ReachCrew
Go to Settings → WhatsApp → Integrations. On the Cloudflare R2 card, click Connect.
Four fields, then Connect and test.
5
Fill in the four fields
Field
Where the value is
Account ID
Step 2.
Bucket name
The bucket’s name exactly as listed under R2 object storage.
Access key ID
Shown when you created the token in step 3.
Secret access key
Shown once, next to the access key ID.
6
Click Connect & test
ReachCrew sends one HeadBucket request to your bucket with these credentials. If it succeeds, the card shows Connected.
You do not need to set up CORS on the bucket. Recordings are uploaded by the recorder extension’s background service worker, which has its own host permissions, so the browser does not apply CORS to those requests.
The Cloudflare R2 card shows Connected and a Last verified time.
To test end to end, place a short recorded WhatsApp call as described in Calling. After the call ends, an object appears in your bucket under calls/, and the recording plays on the call.
R2 answered 404. Check the Bucket name against the list in the Cloudflare dashboard, including hyphens and case.
R2 rejected these credentials for that bucket
R2 answered 401 or 403. The Access Key ID or Secret Access Key is wrong, or the token is not scoped to this bucket, or it lacks Object Read & Write. Create a new token and paste both values again.
Could not reach R2 with this account ID
Any other failure. Check the Account ID for typos and that your server can reach the internet. If the bucket was created with a jurisdiction (EU, FedRAMP), its endpoint differs from the standard one ReachCrew uses, so use a bucket without a jurisdiction.
Cloudflare R2 is not connected — connect it in Settings → WhatsApp → Integrations
A feature needing R2 (recording, playback, photos) answered 409 because this organization has not connected it. Connect it as above.
storage answered 403
The recorder extension reports storage answered followed by R2’s status when its upload fails. A 403 usually means the token cannot write to this bucket. Check the token has Object Read & Write on it, re-save the credentials, then place a new call.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"ReachCrew","description":"The open-source AI SDR: find leads, reach them on email, LinkedIn and WhatsApp, and let AI work every reply, from one workspace you run yourself.","colors":{"primary":"#335cff","light":"#5b7bff","dark":"#1f3bad"},"logo":{"light":"https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/logo-light.svg?fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=5f8cfd64a1c15e42c96a309144aac39a","dark":"https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/logo-dark.svg?fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=c8de838080aa865de4a2aecef52e8077","href":"https://reachcrew.ai"},"favicon":"https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/logo.svg?fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=850db680a389217504579943797affd4","navbar":{"links":[{"href":"/help","label":"Get help"},{"href":"https://reachcrew.ai","label":"Website"}],"primary":{"type":"github","href":"https://github.com/rishav887/reachcrew"}},"footer":{"socials":{"github":"https://github.com/rishav887/reachcrew","website":"https://reachcrew.ai"},"links":[{"header":"Community","items":[{"label":"Ask a question","href":"https://github.com/rishav887/reachcrew/discussions"},{"label":"Report a bug","href":"https://github.com/rishav887/reachcrew/issues/new?template=bug_report.yml"},{"label":"Request a feature","href":"https://github.com/rishav887/reachcrew/issues/new?template=feature_request.yml"},{"label":"Report a security issue","href":"https://github.com/rishav887/reachcrew/security/advisories/new"}]},{"header":"Project","items":[{"label":"GitHub","href":"https://github.com/rishav887/reachcrew"},{"label":"Changelog","href":"https://github.com/rishav887/reachcrew/blob/main/CHANGELOG.md"},{"label":"Contributing","href":"https://github.com/rishav887/reachcrew/blob/main/CONTRIBUTING.md"},{"label":"License (MIT)","href":"https://github.com/rishav887/reachcrew/blob/main/LICENSE"}]}]},"icons":{"library":"lucide"},"contextual":{"options":["copy","view","chatgpt","claude"],"display":"header"},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"recahcrew","hasAudioNarration":false,"actualSubdomain":"recahcrew","customDomains":[],"gitSource":{"type":"github","owner":"rishav887","repo":"linkedin-outreach-platform","deployBranch":"main","contentDirectory":"docs","isPrivate":true,"mountPath":"","label":"","_id":"6af6f68f902237199e0ed1f3"},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6af6f6ac902237199e0ed38d","clientVersion":"0.0.4124","searchFilterCounts":[{"language":"default","tag":"Guides","count":29},{"language":"default","tag":"Self-hosting","count":29},{"language":"default","tag":"Integrations","count":9},{"language":"default","tag":"AI CRM","count":5},{"language":"default","tag":"Developers","count":4},{"language":"default","tag":"Get started","count":4},{"language":"default","tag":"Email","count":3},{"language":"default","tag":"Leads","count":3},{"language":"default","tag":"LinkedIn","count":3},{"language":"default","tag":"Tables","count":3}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-rbac-roles","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"integrations/cloudflare-r2","slug":"/integrations/cloudflare-r2","pageMetadata":{"title":"Cloudflare R2","description":"Create a Cloudflare R2 bucket and API token, connect it to ReachCrew, and store WhatsApp call recordings and contact photos privately.","icon":"cloud","href":"/integrations/cloudflare-r2"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Create a Cloudflare R2 bucket and API token, connect it to ReachCrew, and store WhatsApp call recordings and contact photos privately.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{Accordion,AccordionGroup,Card,CardGroup,Check,Frame,Heading,Info,Mermaid,Note,OptimizedImage,Step,Steps,Table}=_components;if(!Accordion)_missingMdxReference(`Accordion`,true);if(!AccordionGroup)_missingMdxReference(`AccordionGroup`,true);if(!Card)_missingMdxReference(`Card`,true);if(!CardGroup)_missingMdxReference(`CardGroup`,true);if(!Check)_missingMdxReference(`Check`,true);if(!Frame)_missingMdxReference(`Frame`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Info)_missingMdxReference(`Info`,true);if(!Mermaid)_missingMdxReference(`Mermaid`,true);if(!Note)_missingMdxReference(`Note`,true);if(!OptimizedImage)_missingMdxReference(`OptimizedImage`,true);if(!Step)_missingMdxReference(`Step`,true);if(!Steps)_missingMdxReference(`Steps`,true);if(!Table)_missingMdxReference(`Table`,true);return _jsxs(_Fragment,{children:[_jsx(_components.p,{children:`Cloudflare R2 is the file storage ReachCrew uses for WhatsApp call recordings and contact photos. You need it before you place recorded calls. Without it those two features are off.`}),`\n`,_jsxs(Info,{children:[_jsx(_components.p,{children:_jsx(_components.strong,{children:`What you need`})}),_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`A Cloudflare account with an R2 subscription. Cloudflare bills R2 usage to you directly, with a free allowance each month. Check `,_jsx(_components.a,{href:`https://developers.cloudflare.com/r2/pricing/`,children:`R2 pricing`}),` for current terms.`]}),`\n`,_jsxs(_components.li,{children:[`The `,_jsx(_components.strong,{children:`owner`}),` or `,_jsx(_components.strong,{children:`admin`}),` role in ReachCrew.`]}),`\n`,_jsx(_components.li,{children:`About 10 minutes.`}),`\n`]})]}),`\n`,_jsx(Heading,{level:`2`,id:`overview`,children:`Overview`}),`\n`,_jsx(_components.p,{children:`ReachCrew stores two kinds of files in your bucket:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Call recordings`}),`, under `,_jsx(_components.code,{children:`calls/YYYY/MM/`}),` (one file per call, Opus audio in a `,_jsx(_components.code,{children:`.webm`}),` or `,_jsx(_components.code,{children:`.ogg`}),` container).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Contact photos`}),`, under `,_jsx(_components.code,{children:`photos/people/`}),`.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`Nothing in the bucket is public. The recorder extension uploads each recording with a `,_jsx(_components.strong,{children:`presigned URL`}),` that ReachCrew creates for that one file, and it expires after 15 minutes. Playback in the app uses a presigned link that expires after 5 minutes. Contact photos are shown through a link valid for 1 hour. The Cloudflare key you save never leaves the server.`]}),`\n`,_jsx(Mermaid,{chart:`flowchart LR\n E[Recorder extension] -- \"presigned PUT, 15 min\" --> R[(R2 bucket)]\n A[ReachCrew server] -- \"creates presigned URLs\" --> E\n B[Browser playback] -- \"presigned GET, 5 min\" --> R`}),`\n`,_jsx(Heading,{level:`2`,id:`connect-cloudflare-r2`,children:`Connect Cloudflare R2`}),`\n`,_jsxs(Steps,{children:[_jsx(Step,{title:`Create a bucket`,children:_jsxs(_components.p,{children:[`In the `,_jsx(_components.a,{href:`https://dash.cloudflare.com`,children:`Cloudflare dashboard`}),`, open `,_jsx(_components.strong,{children:`Storage \u0026 databases → R2 object storage`}),` and create a bucket (see `,_jsx(_components.a,{href:`https://developers.cloudflare.com/r2/buckets/create-buckets/`,children:`Cloudflare’s guide`}),`). A bucket name uses only lowercase letters, numbers and hyphens, 3 to 63 characters, and does not start or end with a hyphen. Leave it private (the default). If R2 is not enabled yet, Cloudflare asks you to add an R2 subscription first.`]})}),_jsx(Step,{title:`Copy your Account ID`,children:_jsxs(_components.p,{children:[`ReachCrew builds the storage address `,_jsx(_components.code,{children:`https://ACCOUNT_ID.r2.cloudflarestorage.com`}),` from it. In the dashboard, press `,_jsx(_components.code,{children:`Cmd/Ctrl + K`}),`, type `,_jsx(_components.strong,{children:`Copy account ID`}),` and select the result. Other places are listed in `,_jsx(_components.a,{href:`https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/`,children:`Cloudflare’s guide`}),`.`]})}),_jsx(Step,{title:`Create an R2 API token`,children:_jsxs(_components.p,{children:[`On the R2 object storage page, click `,_jsx(_components.strong,{children:`Manage`}),` next to `,_jsx(_components.strong,{children:`API Tokens`}),` (under Account Details). Click `,_jsx(_components.strong,{children:`Create Account API token`}),` (or `,_jsx(_components.strong,{children:`Create User API token`}),`). Under Permissions choose `,_jsx(_components.strong,{children:`Object Read \u0026 Write`}),`, and scope the token to the bucket you created. Create it. Cloudflare shows an `,_jsx(_components.strong,{children:`Access Key ID`}),` and a `,_jsx(_components.strong,{children:`Secret Access Key`}),`. Copy both now: the secret is shown only once. Details are in `,_jsx(_components.a,{href:`https://developers.cloudflare.com/r2/api/tokens/`,children:`Cloudflare’s token guide`}),`.`]})}),_jsxs(Step,{title:`Open the Integrations page in ReachCrew`,children:[_jsxs(_components.p,{children:[`Go to `,_jsx(_components.strong,{children:`Settings → WhatsApp → Integrations`}),`. On the `,_jsx(_components.strong,{children:`Cloudflare R2`}),` card, click `,_jsx(_components.strong,{children:`Connect`}),`.`]}),_jsx(Frame,{caption:`Four fields, then Connect and test.`,children:_jsx(OptimizedImage,{src:`https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/screenshots/app/r2-connect.png?fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=b2d02e932e6137487196f83be9eced6a`,alt:`The Connect Cloudflare R2 dialog with fields for account ID, bucket name, access key ID and secret access key`,\"data-og-width\":`2880`,width:`2880`,\"data-og-height\":`1800`,height:`1800`,\"data-path\":`assets/screenshots/app/r2-connect.png`,\"data-optimize\":`true`,\"data-opv\":`3`,srcset:`https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/screenshots/app/r2-connect.png?w=280\u0026fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=4578f6ceeb342602380913fe5c33b832 280w, https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/screenshots/app/r2-connect.png?w=560\u0026fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=7e34519b46b9c5a08ac14e7abf705930 560w, https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/screenshots/app/r2-connect.png?w=840\u0026fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=372168a82a12c1c78a921e830a7f4550 840w, https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/screenshots/app/r2-connect.png?w=1100\u0026fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=4589ae3ed75ef39476efb9887d9a6d37 1100w, https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/screenshots/app/r2-connect.png?w=1650\u0026fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=47dea97d62ce6359c661e1745f5a6599 1650w, https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/screenshots/app/r2-connect.png?w=2500\u0026fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=279ddcfb8cffb565d9ea0e714c345034 2500w`})})]}),_jsx(Step,{title:`Fill in the four fields`,children:_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Field`}),_jsx(_components.th,{children:`Where the value is`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Account ID`})}),_jsx(_components.td,{children:`Step 2.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Bucket name`})}),_jsx(_components.td,{children:`The bucket’s name exactly as listed under R2 object storage.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Access key ID`})}),_jsx(_components.td,{children:`Shown when you created the token in step 3.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`Secret access key`})}),_jsx(_components.td,{children:`Shown once, next to the access key ID.`})]})]})]})}),_jsx(Step,{title:`Click Connect \u0026 test`,children:_jsxs(_components.p,{children:[`ReachCrew sends one `,_jsx(_components.code,{children:`HeadBucket`}),` request to your bucket with these credentials. If it succeeds, the card shows `,_jsx(_components.strong,{children:`Connected`}),`.`]})})]}),`\n`,_jsx(Note,{children:_jsx(_components.p,{children:`You do not need to set up CORS on the bucket. Recordings are uploaded by the recorder extension’s background service worker, which has its own host permissions, so the browser does not apply CORS to those requests.`})}),`\n`,_jsx(Heading,{level:`2`,id:`check-that-it-works`,children:`Check that it works`}),`\n`,_jsx(Check,{children:_jsxs(_components.p,{children:[`The `,_jsx(_components.strong,{children:`Cloudflare R2`}),` card shows `,_jsx(_components.strong,{children:`Connected`}),` and a `,_jsx(_components.strong,{children:`Last verified`}),` time.`]})}),`\n`,_jsxs(_components.p,{children:[`To test end to end, place a short recorded WhatsApp call as described in `,_jsx(_components.a,{href:`/whatsapp/calling`,children:`Calling`}),`. After the call ends, an object appears in your bucket under `,_jsx(_components.code,{children:`calls/`}),`, and the recording plays on the call.`]}),`\n`,_jsx(Heading,{level:`2`,id:`troubleshooting`,children:`Troubleshooting`}),`\n`,_jsxs(AccordionGroup,{children:[_jsx(Accordion,{title:`Bucket \"name\" does not exist`,id:`bucket-name-does-not-exist`,children:_jsxs(_components.p,{children:[`R2 answered 404. Check the `,_jsx(_components.strong,{children:`Bucket name`}),` against the list in the Cloudflare dashboard, including hyphens and case.`]})}),_jsx(Accordion,{title:`R2 rejected these credentials for that bucket`,id:`r2-rejected-these-credentials-for-that-bucket`,children:_jsxs(_components.p,{children:[`R2 answered 401 or 403. The Access Key ID or Secret Access Key is wrong, or the token is not scoped to this bucket, or it lacks `,_jsx(_components.strong,{children:`Object Read \u0026 Write`}),`. Create a new token and paste both values again.`]})}),_jsx(Accordion,{title:`Could not reach R2 with this account ID`,id:`could-not-reach-r2-with-this-account-id`,children:_jsxs(_components.p,{children:[`Any other failure. Check the `,_jsx(_components.strong,{children:`Account ID`}),` for typos and that your server can reach the internet. If the bucket was created with a jurisdiction (EU, FedRAMP), its endpoint differs from the standard one ReachCrew uses, so use a bucket without a jurisdiction.`]})}),_jsx(Accordion,{title:`Cloudflare R2 is not connected — connect it in Settings → WhatsApp → Integrations`,id:`cloudflare-r2-is-not-connected-connect-it-in-settings-whatsapp-integrations`,children:_jsx(_components.p,{children:`A feature needing R2 (recording, playback, photos) answered 409 because this organization has not connected it. Connect it as above.`})}),_jsx(Accordion,{title:`storage answered 403`,id:`storage-answered-403`,children:_jsxs(_components.p,{children:[`The recorder extension reports `,_jsx(_components.code,{children:`storage answered`}),` followed by R2’s status when its upload fails. A 403 usually means the token cannot write to this bucket. Check the token has `,_jsx(_components.strong,{children:`Object Read \u0026 Write`}),` on it, re-save the credentials, then place a new call.`]})})]}),`\n`,_jsx(Heading,{level:`2`,id:`next`,children:`Next`}),`\n`,_jsxs(CardGroup,{cols:2,children:[_jsx(Card,{title:`WhatsApp calling`,icon:`phone`,href:`/whatsapp/calling`,children:_jsx(_components.p,{children:`Place and record calls.`})}),_jsx(Card,{title:`OpenRouter`,icon:`sparkles`,href:`/integrations/openrouter`,children:_jsx(_components.p,{children:`Turn on call transcription.`})}),_jsx(Card,{title:`Unipile`,icon:`link`,href:`/integrations/unipile`,children:_jsx(_components.p,{children:`The connection that links your WhatsApp numbers.`})}),_jsx(Card,{title:`Integrations overview`,icon:`plug`,href:`/integrations`,children:_jsx(_components.p,{children:`All integrations and who can connect them.`})})]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Cloudflare R2","description":"Create a Cloudflare R2 bucket and API token, connect it to ReachCrew, and store WhatsApp call recordings and contact photos privately.","icon":"cloud","href":"/integrations/cloudflare-r2"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Overview","slug":"overview","depth":2,"children":[]},{"title":"Connect Cloudflare R2","slug":"connect-cloudflare-r2","depth":2,"children":[]},{"title":"Check that it works","slug":"check-that-it-works","depth":2,"children":[]},{"title":"Troubleshooting","slug":"troubleshooting","depth":2,"children":[]},{"title":"Next","slug":"next","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"recahcrew","siteName":"ReachCrew","title":"Cloudflare R2 - ReachCrew","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-11-13T11:38:39.015Z","originalFileLocation":"integrations/cloudflare-r2.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Create a Cloudflare R2 bucket and API token, connect it to ReachCrew, and store WhatsApp call recordings and contact photos privately.\">\u003cmeta data-mint-head name=\"application-name\" content=\"ReachCrew\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Cloudflare R2 - ReachCrew\">\u003cmeta data-mint-head property=\"og:description\" content=\"Create a Cloudflare R2 bucket and API token, connect it to ReachCrew, and store WhatsApp call recordings and contact photos privately.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"ReachCrew\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://recahcrew.mintlify.app/integrations/cloudflare-r2\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://recahcrew.mintlify.site/_mintlify/api/og/v2?division=Integrations\u0026amp;title=Cloudflare+R2\u0026amp;description=Create+a+Cloudflare+R2+bucket+and+API+token%2C+connect+it+to+ReachCrew%2C+and+store+WhatsApp+call+recordings+and+contact+photos+privately.\u0026amp;theme=2714b6d21ebe045728904557\u0026amp;signature=6c9e93c262452ea5c73f2b6222f8862983c80aea07c5369836abc0de28b1f8da\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Cloudflare R2 - ReachCrew\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Create a Cloudflare R2 bucket and API token, connect it to ReachCrew, and store WhatsApp call recordings and contact photos privately.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://recahcrew.mintlify.site/_mintlify/api/og/v2?division=Integrations\u0026amp;title=Cloudflare+R2\u0026amp;description=Create+a+Cloudflare+R2+bucket+and+API+token%2C+connect+it+to+ReachCrew%2C+and+store+WhatsApp+call+recordings+and+contact+photos+privately.\u0026amp;theme=2714b6d21ebe045728904557\u0026amp;signature=6c9e93c262452ea5c73f2b6222f8862983c80aea07c5369836abc0de28b1f8da\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/recahcrew/PDKJgs23Hy0bUg-G/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"ReachCrew\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#335cff\">\u003clink data-mint-head rel=\"canonical\" href=\"https://recahcrew.mintlify.app/integrations/cloudflare-r2\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/integrations/cloudflare-r2.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://recahcrew.mintlify.app/#organization\",\"name\":\"ReachCrew\",\"url\":\"https://recahcrew.mintlify.app\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/recahcrew/b3LOEBKCy3btecGb/assets/logo-light.svg?fit=max\u0026auto=format\u0026n=b3LOEBKCy3btecGb\u0026q=85\u0026s=5f8cfd64a1c15e42c96a309144aac39a\"}},{\"@type\":\"WebSite\",\"@id\":\"https://recahcrew.mintlify.app#website\",\"name\":\"ReachCrew\",\"url\":\"https://recahcrew.mintlify.app\",\"publisher\":{\"@id\":\"https://recahcrew.mintlify.app/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://recahcrew.mintlify.app/integrations/cloudflare-r2#webpage\",\"url\":\"https://recahcrew.mintlify.app/integrations/cloudflare-r2\",\"name\":\"Cloudflare R2\",\"description\":\"Create a Cloudflare R2 bucket and API token, connect it to ReachCrew, and store WhatsApp call recordings and contact photos privately.\",\"dateModified\":\"2026-11-13T11:38:39.015Z\",\"isPartOf\":{\"@id\":\"https://recahcrew.mintlify.app#website\"},\"breadcrumb\":{\"@id\":\"https://recahcrew.mintlify.app/integrations/cloudflare-r2#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://recahcrew.mintlify.app/integrations/cloudflare-r2#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Integrations\",\"item\":\"https://recahcrew.mintlify.app/integrations\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Cloudflare R2\",\"item\":\"https://recahcrew.mintlify.app/integrations/cloudflare-r2\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://recahcrew.mintlify.app/integrations/cloudflare-r2#article\",\"headline\":\"Cloudflare R2\",\"name\":\"Cloudflare R2\",\"description\":\"Create a Cloudflare R2 bucket and API token, connect it to ReachCrew, and store WhatsApp call recordings and contact photos privately.\",\"url\":\"https://recahcrew.mintlify.app/integrations/cloudflare-r2\",\"mainEntityOfPage\":{\"@id\":\"https://recahcrew.mintlify.app/integrations/cloudflare-r2#webpage\"},\"dateModified\":\"2026-11-13T11:38:39.015Z\",\"publisher\":{\"@id\":\"https://recahcrew.mintlify.app/#organization\"},\"isPartOf\":{\"@id\":\"https://recahcrew.mintlify.app#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"ReachCrew\",\"publisher\":{\"@type\":\"Organization\",\"name\":\"ReachCrew\"},\"provider\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","agentPointerHref":"/integrations/cloudflare-r2.md","siteName":"ReachCrew","nav":{"global":null,"tabs":[{"tab":"Guides","pages":[{"title":"Guides","href":""}]},{"tab":"Self-hosting","groups":[{"group":"Self-hosting","pages":[{"title":"Self-hosting","icon":"server","href":"/self-hosting"},{"title":"Docker Compose","icon":"container","href":"/self-hosting/docker-compose"},{"title":"From source","icon":"terminal","href":"/self-hosting/from-source"},{"title":"Dokploy on a VPS","icon":"server-cog","href":"/self-hosting/dokploy"},{"title":"Your own domain","icon":"globe","href":"/self-hosting/custom-domain"},{"title":"Logs and dashboards (Grafana)","icon":"chart-line","href":"/self-hosting/observability"},{"title":"Going to production","icon":"rocket","href":"/self-hosting/production"},{"title":"Public demo","icon":"presentation","href":"/self-hosting/demo"},{"title":"Troubleshooting","icon":"life-buoy","href":"/self-hosting/troubleshooting"}]},{"group":"Configuration","pages":[{"title":"Configuration reference","icon":"sliders-horizontal","href":"/configuration"}]},{"group":"Integrations","pages":[{"title":"Integrations","icon":"plug","href":"/integrations"},{"title":"Google Workspace","icon":"mail","href":"/integrations/google-workspace"},{"title":"Gmail reply sync","icon":"inbox","href":"/integrations/gmail-reply-sync"},{"title":"Unipile","description":"Connect Unipile so ReachCrew can use LinkedIn and WhatsApp: get your DSN and access token, connect, link accounts and check webhooks.","icon":"link","href":"/integrations/unipile"},{"title":"Cloudflare R2","icon":"cloud","href":"/integrations/cloudflare-r2"},{"title":"OpenRouter","description":"Create OpenRouter keys, add credits and provider keys, connect them in ReachCrew and choose the models, with provider pinning and transcription.","icon":"sparkles","href":"/integrations/openrouter"},{"title":"Enrichment providers","icon":"zap","href":"/integrations/enrichment-providers"},{"title":"Resend","icon":"send","href":"/integrations/resend"},{"title":"Google sign-in","icon":"log-in","href":"/integrations/google-sign-in"}]},{"group":"Policies","pages":[{"title":"Responsible use","icon":"scale","href":"/responsible-use"}]}]},{"tab":"Developers","pages":[{"title":"Developers","href":"/development"}]}]},"basePath":"","title":"Cloudflare R2 - ReachCrew","buildId":"dpl_8hoxaqFTU7hTr6A2fUraxAyjuiZo","deploymentId":"dpl_8hoxaqFTU7hTr6A2fUraxAyjuiZo","dynamics":["07c022d89b","a74be98ba7"],"chunks":["Frame-Dmj010Ph.js","DynamicMarkdown-GHvayGOn.js","zoom-image-lightbox-8gCTkA22.js","lightbox-handle-r17bVUz3.js"]}