Overview
Getting Started
Widgets
Categories
Keywords
Reviews
Users
Businesses
Businesses Search
Negotiations
Messages
Requests
On-Demand Orders
Help
Changelog
Terms and Policies
The Request Flow Widget (RFW) allows you to render Thumbtack's Request Flow directly on your platform. To play around with a live example of the RFW kindly visit the RFW Playground.
Make an API call to the /businesses/search endpoint to retrieve the widgets.requestFlowURL for each Pro. The response from /businesses/search will contain a data key, which will contain as many Pros as are available (up to a limit of 30).
We recommend rendering ALL Pros returned in data - with each Pro having a unique CTA. When said Pro’s CTA is clicked, it should open a Modal with an iframe with the src attribute being the corresponding Pro’s widgets.requestFlowURL.
Load said widgets.requestFlowURL within an iframe in a modal when the user selects a Pro or clicks on the Pro’s CTA. The widgets.requestFlowURL will be structured as follows:
{{environment}}/embed/request-flow?category_pk={{category_pk}}&service_pk={{service_pk}}&zip_code={{zip_code}}&utm_medium=partnerships&utm_source={{utm_source}}
All RFW parameters are listed below:
Parameter | Description | Default Value | Valid Values | Required |
category_pk | The ID of the category you are searching in. | N/A | yes | |
service_pk | The Businesses' Service ID. | N/A | yes | |
zip_code | The zip_code this Pro will be performing work in. | IP-inferred | no | |
utm_medium | The attribution channel. | N/A | partnership | yes |
utm_source | The utm_source assigned to your Partner account. | N/A | Must be prefixed by cma- | yes |
utm_ prefixed UTM Parameters | Any additional UTM Parameters. | N/A | no |
<Modal isOpen={isModalOpen}>{isModalOpen &&<iframesrc="<request_flow_iframe_url>"sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"width="632px"height="600px"/>}</Modal>
- The Thumbtack Request Flow is responsive, so you can customize the width and height to fit your design needs.
- You’ll want to also add some CSS to remove the border that comes with iframes by default.
- This is why each of values within sandbox are needed:
- allow-scripts and allow-same-origin are needed for the iframe to load.
- allow-forms is needed for form submission to work within the iframe.
- allow-popups is needed so that users can click on important links such as our Terms & Conditions and have these links open in a new tab.
- allow-popups-to-escape-sandbox is needed so that the links that open in a new tab are not subject to the same restrictions as the iframe itself.
Implement an event listener for when the modal should close (depending on the platform).
useEffect(() => {const handleMessageEvent = (event: MessageEvent): void => {if (event.data === 'THUMBTACK_RF_CLOSE') {setIsModalOpen(false);}};window.addEventListener('message', handleMessageEvent);return () => {window.removeEventListener('message', handleMessageEvent);};}, []);
func makeThumbtackWebView() -> WKWebView {let configuration = WKWebViewConfiguration()let userContentController = WKUserContentController()let rfCloseScript = WKUserScript(source: """window.addEventListener('message', function(event) {if (event.data === 'THUMBTACK_RF_CLOSE') {window.webkit.messageHandlers.rfClose.postMessage('close');}});""",injectionTime: .atDocumentEnd,forMainFrameOnly: false)userContentController.add(self, name: "rfClose")userContentController.addUserScript(rfCloseScript)configuration.userContentController = userContentControllerlet webView = WKWebView(frame: .zero, configuration: configuration)var request = URLRequest(url: url)// Set referer domainrequest.setValue("<YOUR_DOMAIN>", forHTTPHeaderField: "Referer")webView.load(request)return webView}// Handle the close event, this is generally your UIViewController, or UIViewRepresentable.Coordinator.extension MyMessageHandler: WKScriptMessageHandler {func userContentController(_ userContentController: WKUserContentController,didReceive message: WKScriptMessage) {guard message.name == "rfClose" else { return }dismiss()}}
Last Updated: Apr 23rd, 2025
On this page