This is an old revision of the document!
This document is directed to developers implementing a destination for Real Geeks Leads. If you're a client trying to connect to a destination visit this page
First send a message to our Support asking for credentials to create a Lead Router Destination. We will give you a Real Geeks website and Real Geeks account.
Then visit our Lead Router config, login with the Real Geeks credentials given and follow the steps to configure a Custom API as destination.
At first your Custom API destination will be available only for your test site, so you can use your website, sign-up, perform searches, etc, to trigger requests to your API.
When you're ready to go Live let us know and provide the following information:
We have two main entities: Lead and Activity. A lead is somebody with name and contact information that signed up in a website. An activity is something a lead has done, like performing a search, viewing a property, marking a property as favorite, etc.
A destination is a single url that accepts HTTP POST requests with a JSON body. There are four types of requests sent to this url:
We identify which type of request is being sent with the action
field.
Somebody signed up on the website, it will contain contact information and at least one activity. See Lead fields below.
Lead action
field will be set to “created”
. activities
field is always present too, indicating the first actions the user performed on the website.
Lead contact details were updated on the website. Lead action
field will be set to “updated”
. Lead identification fields will also be available: id
, site_uuid
, site_domain
and partner_id
activities
could be present too, in this case the lead details should be updated and these activities should be added. Activities are never removed or updated, only added.
One or more activities are being added to a Lead. For this request the following fields are set:
id
: Lead id, same as given on creationsite_uuid
site_domain
action
: “activity_added”
partner_id
activities
: list of one or more activities. See Activity fields below.Activity Added requests are always sent after the corresponding New Lead requests. But sometimes they can arrive in a different order since the network isn't always reliable.
Real Geeks websites have a property valuation tool, users enter an address and it provides an estimate sale value for that property. It also shows the last sold date and price.
In order to visualize this information users have to sign up, giving their contact details. If the user doesn't finish the sign up process their contact information is unknown, all we have is the address they looked for. This is called a Potential Seller Lead.
All fields a lead could possibly have. If a field is optional it might not be present in the JSON.
Field name | Type | Presence | Description |
---|---|---|---|
action | string | required | Identify what type of request this is, one of “created” , “updated” or “activity_added” . Sometimes the value “unknown” is given, this is not common but happens if the client is using a lead source that does not identify which action was performed, should be handled as “update or create if doesn't exist” |
id | string | required | Every lead has a unique identifier, used when updating the lead or adding activities to it |
partner_id | string | required | The ID provided by the Real Geeks client when connecting to this destination |
source | string | required | Tells where the lead came from. See Source values below |
site_uuid | string | required | The unique identifier of the site inside Real Geeks where this lead originated from. If you intend to reference the site inside your system prefer to use this id instead of the site_domain because sometimes clients change the site domain |
site_domain | string | required | The domain of the site that originated the lead |
site_login_url | string | required | An url agents can use to login as this lead on the website |
agent_name | string | optional | If this lead was generated from an Agent Page on the website, the agent full name will be given here |
agent_email | string | optional | If this lead was generated from an Agent Page on the website, the agent email will be given here |
email | string | required | Valid email address of the lead |
first_name | string | required | Lead first name |
last_name | string | optional | Lead last name, not always given |
role | string | required | One of “Seller”, “Buyer” or “Potential Seller” |
phone | string | optional | Phone number. US numbers could be formatted as 808-123-1234 or (808) 123-1234 . International number format: +55 83 1234-1234 . Note that a valid format is not guaranteed, since some clients have other lead sources besides their website |
activities | list of Activity | optional | List of activities, see Activity fields below |
created | string | required | Timestamp of when this lead was created, using ISO 8601, ex.: “2015-04-01T08:41:51+00:00” |
These are know values source
field could have
But since clients can have custom lead sources using Zapier or our Incoming API, this field could have any value any other string value
Field name | Type | Presence | Description |
---|---|---|---|
type | string | required | See Type values below |
description | string | required | Human readable description |
property | Property | optional | In case this activity was related to a specific property. If present it is a document with Property fields |
created | string | optional | Timestamp of when this activity was created, using ISO 8601, ex.: “2015-04-01T08:41:51+00:00” |
source | string | required | Tells where the activity came from. Can be the same set of values as Lead source field |
Activity type indicates what action was performed by the lead. It's one of these values:
“visited”
: user visited the first for the first time (initial sign-up) or is back to the site after two hours of inactivity“search_performed”
: user performed a search on the website. Description will contain a summary of the search filters“saved_search_added”
: user not only performed a search but also saved it to their profile. Description will contain a summary of the search filters“saved_search_removed”
: user removed a saved search. Description is the same as “saved_search_added”
“property_viewed”
: user visualized a property on the website. Description contains a summary of the property details and a link to it.“favorite_property_added”
: user marked a property as favorite. Description will contain a summary of the property and a link to it on the website.“favorite_property_removed”
: user removed a favorited property. Description is the same as “favorite_property_added”
“property_updates_email_sent”
: our systems sent an email to the user with updates about their saved searches and favorited properties, with information like price changes and new properties available“bad_email_flagged”
: our systems detected that the email address this user signed up with is not valid, emails failed to be sent to it“bad_email_unflagged”
: our systems can now send emails to this user's email address“opted_out”
: user opted out of our property updates emails“deleted_from_website”
: user account was deleted from the website. Not that other systems, like our Lead Manager, could still have this lead saved.“contact_emailed”
: user sent a contact message.“valuation_inquire”
: user used the property valuation tool to see the estimated value of a property.“shared_property_via_email”
: a lead has shared a property by email with somebody else, the description will contain the property url and the name and email of the person they shared it with.“market_report_viewed”
: user viewed a market report on the site.“saved_market_report”
: user saved a market report page in their saved searches.“coming_soon_viewed”
: user viewed a coming soon page on the site.“note”
: agent has added an internal note about this lead on the Lead Manager. The note itself will be in the activity description
Field name | Type | Presence | Description |
---|---|---|---|
mls | string | optional | MLS number |
url | string | optional | Full absolute url to visualize property |
street_address | string | optional | Street portion of the address, ex: “123 Keolu Rd Unit 12” |
city | string | optional | |
state | string | optional | |
zip | string | optional | |
beds | integer | optional | Number of bedrooms |
full_baths | integer | optional | Number of full bathrooms |
half_baths | integer | optional | Number of half bathrooms |
type | string | optional | Human readable type, ex: “Single Family Home” or “Condo” |
sqft | string | optional | Property square feet, has no format requirement |
listing_price | string | optional | If the property is being sold this is the price it's being listed for. The supported format is a number with optional decimal places, ex.: “100500” or “100500.90” |
listing_status | string | optional | If the property is being sold this is the current status, any format is allowed. ex.: “For Sale” |
listing_days_on_market | integer | optional | If the property is being sold, how long it's been in the market |
estimated_value | string | optional | Estimated value this property could be sold for, useful for Seller or Potential Seller leads. Could be from an automated or personalized valuation. The supported format is a number with optional decimal places, ex.: “100500” or “100500.90” |
last_sold_date | string | optional | Doesn't have any format requirement, could be something like “2015-12-25” |
last_sold_price | string | optional | If present will be a number with optional decimal places, ex.: “100500” or “100500.90” |
tags | []string | optional | Search tags, could be address components or any other field that destinations might use to easily implement a search feature. Real Geeks websites send things like street address, neighborhood, state, island, mls number, etc. i.e. [“kailua”, “212”, “oahu”, “981112”] |
latitude | float | optional | |
longitude | float | optional | |
images | []string | optional | List of images urls of this property |
year_built | int | optional | The year this property was built |
list_date | string | optional | Date when this property was listed. Example: “2014-10-02T00:00:00Z” |
All requests will also include the following custom HTTP headers
Contains an id for this message. This is helpful to identify retries of the same request. Here is an example value:
X-Lead-Router-Message-Id: 7ca66bef-c704-47f2-b46e-0638291bae85
Contains the signature of this request body using the configured secret key. See details on our Security section below.
User-Agent
is a string with the format: “RealGeeks-LeadRouter/v1.0”
, where v1.0
is the API version.
Minor changes to the API that shouldn't affect clients will increase the minor version number (v1.1
, v1.2
, etc).
Major changes to the API that could potentially affect clients will be notified beforehand and when they take place the major version number will increase (v2.0
, v3.0
, etc).
Here is an example value:
User-Agent: RealGeeks-LeadRouter/1.0
We retry every time we get a response with status code different than 200
up to 8 times, here is the behavior:
All attempts of the same request will contain the same value of X-Lead-Router-Message-Id
header.
To avoid retries return status code 406
.
In order to receive requests from Real Geeks your API needs to be open to the internet. For security reasons you probably want to limit the requests you receive to those coming from Real Geeks. The easier way to do this is to setup a secret token and validate the request.
When your secret token is set Real Geeks will create a hash with the request body. You can see our full implementation in Go below:
func sign(secret, requestBody []byte) string { mac := hmac.New(sha256.New, secret) mac.Write(requestBody) return fmt.Sprintf("%x", mac.Sum(nil)) }
This hash will be available as header X-Lead-Router-Signature
. We have a full example of APIs in Go and Python validating the signature below. Obviously, your language and server implementations may differ. There's a couple important things to point out:
==
is not advised. Use a constant time string comparison functionSample code in Python using Flask
# API server validating Real Geeks Signature import hmac import hashlib from flask import Flask, request app = Flask(__name__) app.debug = True SECRET = '12345' @app.route("/", methods=["POST"]) def hello(): signature = str(request.headers['X-Lead-Router-Signature']) mac = hmac.new(SECRET, request.data, hashlib.sha256) if not hmac.compare_digest(mac.hexdigest(), signature): return "invalid signature", 403 return "ok" if __name__ == "__main__": app.run(port=9090)
Sample code in Go
// API server validating Real Geeks Signature package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "io/ioutil" "log" "net/http" ) var secret = []byte("12345") // your secret here func main() { http.HandleFunc("/", handleWebhook) log.Print("Listening at 9090") http.ListenAndServe(":9090", nil) } func handleWebhook(w http.ResponseWriter, r *http.Request) { signature, err := hex.DecodeString(r.Header.Get("X-Lead-Router-Signature")) if err != nil { log.Printf("failed to decode signature header (%v)", err) w.WriteHeader(500) return } body, err := ioutil.ReadAll(r.Body) if err != nil { log.Printf("failed to read body (%v)", err) w.WriteHeader(500) return } r.Body.Close() mac := hmac.New(sha256.New, secret) mac.Write(body) mymac := mac.Sum(nil) if !hmac.Equal(mymac, signature) { log.Printf("Signature doesn't match") w.WriteHeader(403) return } log.Print("ok") w.WriteHeader(200) }