Pre-quote analysis

Calendly to Rex: the field-level mapping

Built for the brief "an automation from Calendly to Rex our CRM that will upload the client's details into system and add their appointment into the diary" - before quoting, so the quote is about a known shape and not a guess. Every field the webhook actually sends, where each one lands in Rex, and the four places this breaks. The mapping below runs live in your browser: no network calls, no storage, no tracking. Works offline.

I have not worked inside Rex. You asked for direct experience and I do not have it, so here is the next best thing instead of a claim. This mapping is read off Rex's own public API description, not off a job I have delivered. The Calendly half - webhook subscription, signature verification, payload parsing, name/phone/timezone normalisation - I can build today. The Rex half is one authenticated session against your account away from confirmed: the field names are documented, but the datetime encoding, the appointment-to-contact link shape and two value-list vocabularies are not. Anything on this page I could not read straight out of the docs is tagged INFERRED or UNVERIFIED, in the table, in the JSON, and in the run log.

VERIFIED Read directly from the published API description or a captured webhook payload.
INFERRED Shape reasoned out from how Rex describes the field. No worked example is published.
UNVERIFIED The field is documented, but its accepted values or format are not. One test call settles it.

01 - the call sequence

Five calls per booking, and one trap

Rex is RPC, not REST. Every call is a POST to /v1/rex/{Service}/{method}, so there is no resource URL to reason about - only service names and method names.

  1. Calendly fires invitee.created at your endpoint. Signed with a Calendly-Webhook-Signature header - t=<timestamp>,v1=<hmac>, HMAC-SHA256 over t + "." + rawBody. The signing_key is one you supply in the create-subscription body, not something the response hands back - and it is optional, so a subscription created without one sends no signature at all. Verify on the raw body before parsing, enforce a timestamp window, and reject anything that fails. Return 2xx immediately, then do the Rex work on a queue - a webhook that waits on three CRM calls will eventually time out and be retried, which is how duplicate contacts get created.
  2. POST /v1/rex/Authentication/login {email, password, account_id?, token_lifetime?} returns a token used as Authorization: Bearer. The only documented route is a real user's credentials - not an API key, not OAuth. Default lifetime is 1 hour of activity, maximum 604800 seconds. Cache the token, refresh on expiry, and keep the credentials in a secret store, not in the automation.
  3. POST /v1/rex/Contacts/findPossibleDuplicates Accepts email, phone, name_first, name_last, company_name, contact_type, match_behaviour. This is the guard that stops a repeat booker becoming a second record. Skipping it is the single most common way a booking integration pollutes a CRM.
  4. POST /v1/rex/Contacts/create {data:{...}, return_id:true}. Names, emails and phones are sub-records under related, not top-level strings. return_id gives you the contact id the diary event needs.
  5. POST /v1/rex/CalendarEvents/create The diary entry, linked back to the contact through records. Note the separate Appointments service is the reminder/task object, not the diary event - its fields are reminder_date, reminder_time, remindee_type, and it exposes no create method at all. Picking it because the word fits the brief is how a booking ends up nowhere.

The trap: whether a failure carries an HTTP error is a per-request option

Every response comes back in the envelope {result, error, correlation}, and error is the signal that is always right. Whether the HTTP status agrees with it is a setting - use_status_codes. On the /v1/rex endpoints above it defaults to on, so a rejected value really does return 400 and a stale token 401 VERIFIED - I checked that live against api.rexsoftware.com before writing this. On the older rex.php v0 format it defaults to off, and Rex's own docs say you "will always get HTTP 200 responses" - and any v1 caller can put itself back in that state with one header. So the check has to be on the body: a client that trusts res.ok is one X-Api-Option away from reporting clean runs while dropping bookings on the floor.

POST /v1/rex/Contacts/create
// v1 default: use_status_codes = true
HTTP/1.1 400 Bad Request
{ "result": null,
  "error": { "type": "ValidationException", ... },
  "correlation": { ... } }

POST /rex.php
// v0 default: use_status_codes = false
HTTP/1.1 200 OK
{ "result": null,
  "error": <non-null on failure>,
  "correlation": { ... } }

// right: if (body.error) -> fail, log request_id
// wrong: if (res.ok)     -> success

02 - the mapping

Calendly field to Rex field

The awkward rows are the point. Four fields do not survive a naive copy: the name, the phone, the times, and the contact link.

Calendly (invitee.created)Rex destinationWhat actually happens
payload.name related.contact_names[0].name_first
related.contact_names[0].name_last
related.contact_names[0].name_middle
One string, three fields. Calendly sends a single name; first_name and last_name are null unless the event type has the split name field switched on. Splitting is a guess: "Ana Maria van der Berg" and "Cher" both break the obvious rule. The fix is upstream - turn on the split name field in the Calendly event type and this whole row disappears. Toggle it in the live demo below to see the difference.
payload.email related.contact_emails[0].email_address
+ email_primary: true
Clean copy. email_desc is a documented field but its accepted values are not published UNVERIFIED - one call confirms whether it is free text or a picklist.
payload.questions_and_answers[]
matched on question text
related.contact_phones[0].phone_number The phone is not in the payload. There is no phone field on a Calendly invitee. It only exists if you add a custom question, and it arrives as {answer, question, position} in an array. Matching by position is fragile - reorder the booking form and the automation silently files an address as a phone number. Match on the exact question text, and treat a no-match as a flagged record, not a crash.
payload.text_reminder_number related.contact_phones[0] (fallback) Usually null. It is only populated when the event type has SMS reminders enabled and the invitee opts in. Useful as a fallback, never as the primary source.
(derived from the phone answer) contact_phones[0].system_e164_phone_number Invitees type "07700 900312", "+44 7700 900312", "07700900312". E.164 normalisation is mine to do. Whether Rex derives this server-side or expects it written is not documented INFERRED - I send it, and drop it if Rex rejects it.
payload.scheduled_event.start_time
payload.scheduled_event.end_time
data.starts_at
data.ends_at
Calendly sends UTC, always. A 15:30 London booking arrives as 14:30Z. Whether Rex wants ISO-8601 UTC, an offset, wall time in the account's timezone, or a unix timestamp is not published UNVERIFIED. Getting this wrong puts the appointment in the diary an hour out, twice a year, which is the failure mode nobody notices until a valuer misses an appraisal. The demo below lets you switch the encoding - one test call decides it permanently.
payload.timezone (no field) - description + conversion input This is the invitee's IANA timezone, not your office's. It is what you need to write "15:30 their time" into the note, and it is the wrong input to use for the diary slot if your team's calendar runs on a different zone.
payload.scheduled_event.name data.title The event type's display name, e.g. "Property Appraisal - 30 min". Not scheduled_event.event_type - that field is a URI, and dropping it into a title is a giveaway that nobody read the payload.
payload.scheduled_event.location data.event_location An object, not a string. The confirmed shape is {type, join_url, status}; other location types return a different key set, so it has to be read defensively off type rather than by reaching straight for join_url. Rex's side is structured too - event_location.description is a searchable field on CalendarEvents - so the flat URL string the demo writes is a placeholder UNVERIFIED, not the final shape.
payload.cancel_url
payload.reschedule_url
data.description Cheap and high value: whoever opens the diary entry can cancel or move the booking without leaving Rex and without emailing the client to ask.
payload.uri data.private_note + contact notes The idempotency key. Calendly retries deliveries. Storing the invitee URI is what lets a retry find the existing record instead of creating a second contact and a second diary entry.
payload.rescheduled
payload.old_invitee
(no field) - control flow The obvious read of this field is the wrong one. A reschedule fires both events: invitee.canceled for the old booking, carrying rescheduled: true, and a fresh invitee.created for the new one. So on the create side rescheduled is not the flag to branch on - old_invitee is, because that is where the pointer back to the replaced booking lives. Branch on the wrong one and you get two diary entries for one appointment. It is also why handling reschedules means subscribing to the cancel event too - the same subscription, one more entry in its events array.
(id returned by Contacts/create) data.records[0].contact_id The one shape I had to reason out. INFERRED Rex documents that the contact link lives in records and that it is searchable as records.contact_id, so the write shape is almost certainly records: [{contact_id: N}] - but Rex ships no create example, so I am not calling this confirmed. It is the first thing I would test.
(constant) marketing_enquiry_source Tagging the source is what makes the automation auditable later - "how many of these came from the booking page". This is a Rex value-list field rather than free text - it is searchable as marketing_enquiry_source_id, and allowed entries are read from SystemValues/getCategoryValues - so the demo's plain "Calendly" string is a placeholder until that list is pulled from your account UNVERIFIED.
(none) calendar_id, organiser_user_id,
owner_user_id, appointment_type_id,
status_id, transparency_id, access_level_id
Calendly cannot supply these - they are ids that exist only inside your Rex account. They are configuration, gathered once during setup and pinned, or the appointment lands on nobody's calendar. Listed in the questions below.

03 - live

Run the mapping

Left is a realistic invitee.created payload. Press Run and it becomes the three Rex request bodies. Edit the name, email, phone or time and run it again to watch the derived fields move. Tap or hover any underlined key to light up every place that field lands. Everything is computed in this page - no requests, nothing stored.

The automation is pinned to match Best mobile number exactly. Change one character here - as anyone editing the booking form eventually will - and watch the phone drop out of the Rex body.

Nothing you type leaves this page. No requests, no storage.

In Calendly webhook invitee.created

            
Captured shape. Signed with a Calendly-Webhook-Signature header; subscribed via POST https://api.calendly.com/webhook_subscriptions.
Guard POST api.uk.rexsoftware.com/v1/rex/Contacts/findPossibleDuplicates

            
Placeholder value in this body: match_behaviour - the field is documented, its accepted values are not UNVERIFIED.
Step 1 POST api.uk.rexsoftware.com/v1/rex/Contacts/create

            
Placeholder values in this body: email_desc, phone_type, marketing_enquiry_source - documented fields, undocumented vocabularies UNVERIFIED. Returns the contact id used below.
Step 2 POST api.uk.rexsoftware.com/v1/rex/CalendarEvents/create

            
records shape is INFERRED - no create example is published. The null ids are yours, gathered once at setup. Datetime encoding and the flat event_location string are UNVERIFIED.

Run log - what the code had to decide

    04 - the no-code ceiling

    Why Zapier alone will not finish this job

    Worth knowing before you pay anyone, including me. Both apps are on Zapier and the Calendly trigger is fine. The problem is the Rex side: here is every action Rex exposes there.

    • Send Email
    • Assign Lead
    • Create Lead
    • Create Match Profile
    • Create Note
    • Create Reminder
    • Send SMS
    • Add Tag
    • Remove Tag
    • Create Track
    • Remove All Tracks
    • Create Contact
    • Create Calendar Event

    There is no create-contact action and no create-calendar-event action. Calendar Event is read-only on Zapier - it can be looked at, not written. I also looked for an n8n node and a Make module for Rex and found neither, so this is not a "use a different no-code tool" problem.

    So: the first half of your brief is achievable no-code - a booking can become a Rex Lead plus a Note holding the time, the phone and the links, and for some agencies that is genuinely enough. The second half is not. "Add their appointment into the diary" means CalendarEvents/create, and nothing reaches that endpoint except code holding a login token. That is the whole reason this brief is worth more than a Zap, and the reason the number of bidders who can actually finish it is smaller than the number who bid.

    05 - before I quote

    What I would need from you

    Eight answers. Most take a sentence, and each one is a place the price or the build changes - which is why I would rather ask now than discover them mid-build.

    1. Does it have to land on the Rex calendar, or is a Lead plus a Reminder enough?
    This is the biggest fork in the price. Lead plus Note is a fraction of the work; a real diary event is the coded half above.
    2. Which Rex user will the integration authenticate as?
    Rex's documented login is a real user's email and password - no API key, no OAuth. (Two other login methods sit on the Authentication service; neither is documented for third parties.) If that is a person's account, the automation stops the day they change their password or leave. The right answer is usually a dedicated integration user, and that is your decision, not mine.
    3. Where does the phone number come from, and what is the exact question wording?
    Calendly does not send a phone unless a custom question asks for it. I need the wording character for character, because the match is on that text - and if the question is optional, I need to know what should happen to a booking that arrives without one.
    4. Which Calendly plan are you on, and are you an admin on the organisation?
    Webhooks are paid-plan only - Calendly lists them on Professional, Standard, Standard Plus, Teams, Teams Plus and Enterprise - Free is not on that list. Scope matters just as much: only an owner or admin can create an organization-scoped subscription covering everybody's bookings. A team member's token can only create a user-scoped one covering their own. If several agents take bookings, this decides whether it works at all.
    5. Which calendar_id, organiser_user_id and appointment_type_id should events use?
    Also status_id and access_level_id if you use them. Calendly cannot supply any of these. Either one fixed set, or a rule - for example, the Calendly event type name decides the appointment type.
    6. Dedupe on email, on phone, or always create a new contact?
    Contacts/findPossibleDuplicates exists precisely for this, but the policy is a business call: repeat bookers are common and nobody wants four Sarah Whitfields.
    7. What should a reschedule and a cancellation do?
    A reschedule fires both events - invitee.canceled on the old booking carrying rescheduled: true, then a new invitee.created carrying old_invitee. So reschedules cannot be handled cleanly from the create event alone - invitee.canceled has to be subscribed as well. That is not a second subscription, just one more entry in the events array, but it is more branching. Should the old diary entry move, be deleted, or be left with a note? I would price both events in now rather than as a change request.
    8. Is your Rex account AU or UK?
    Different hosts - api.rexsoftware.com versus api.uk.rexsoftware.com. A one-line difference, but the sort of one-line difference that costs a day if it is assumed.

    And one thing I need from your side to finish it

    Rex has no self-serve developer signup. Its docs name three routes: a test account requested through Rex's CRM API enquiry form, demo credentials from Rex sales, or being invited as a user on your live account - and Rex is explicit that an integration user counts against your agency's user slots unless the integrator has been through their "verified integrator" check. Whoever you hire needs one of those before they can confirm the unverified items on this page - if a bidder has not told you that, they have not looked at the API yet.