projectfit.ai

New match? You’ll know right away.

In your browser, on Telegram, by email, or inside your own workflow.

How a match reaches you

New match
projectfit finds a match

It fits your profile and what you’re looking for.

sends
Browser
Telegram
Webhook
Email

Which option fits you?

OptionBest forSpeedSetup
Browser
Working at your computerInstantEasy
Telegram
Updates wherever you areInstantEasy
Webhook
Your own tools and automationsInstantAdvanced
Email
Your familiar inboxImmediate, daily or weeklyAlready set up

Browser notifications

See new matches on your computer or phone, even when projectfit isn’t open.

1

Open your notification settings in a supported browser and select Enable on this device.

2

Allow notifications when your browser asks. Permission applies only to the current browser profile and device.

3

Repeat setup for up to three devices. Connected devices can be removed individually.

4

On iPhone or iPad, first add projectfit to the Home Screen and open it from there before enabling browser notifications.

  • Update your browser or try a current browser that supports notifications.
  • Allow notifications for projectfit in your browser settings, then reload the page and try again.
  • On iPhone or iPad, add projectfit to your Home Screen and open it from there.
  • Check your device’s notification, Focus, Do Not Disturb, battery, and background settings.
  • Notifications belong to this device, browser profile, and projectfit account. Delivery can occasionally be delayed.

Telegram

Get new matches in your Telegram chat, wherever you are.

1

Select Connect Telegram in your notification settings.

2

Open the projectfit bot using the link in the dialog. The link is valid for 15 minutes.

3

Tap Start in Telegram.

4

Return to projectfit. The connection is verified automatically while the dialog is open.

  • If the link has expired, close the dialog and select Connect Telegram again.
  • Make sure you tap Start in the bot chat before returning to projectfit.
  • If you blocked the projectfit bot, unblock it in Telegram and connect again.

Webhook

Send every new match straight to your tools and automations.

1

Paste the webhook URL from your tool.

2

Save the signing secret somewhere safe. We only show it once.

3

Send a test. Once it works, your webhook is live.

Endpoint requirements

  • The endpoint receives HTTPS POST requests on port 443. URL credentials are not allowed.
  • Every DNS target must resolve to public addresses. projectfit validates the target again for every delivery and pins the validated IP for that request.
  • The request deadline is 10 seconds, redirects are never followed, and outbound request payloads are limited to 512 KiB.

Request headers

  • Content-Type: application/json
  • User-Agent: projectfit-webhooks/1.0
  • x-projectfit-event: event type, such as project.matched or webhook.test
  • x-projectfit-delivery: stable delivery ID; use it to deduplicate at-least-once delivery
  • x-projectfit-timestamp: Unix timestamp in seconds
  • x-projectfit-signature: v1 signature described below

Payload

Every payload contains event. project.matched also contains data; webhook.test contains event only. Nullable fields use null and array fields use [] when they have no value.

FieldTypeDescription
eventobjectEvent metadata included in every webhook payload.
event.idstringStable event and delivery identifier for deduplication.
event.type"project.matched" | "webhook.test"Identifies a project match or a synthetic connection test.
event.schemaVersion"v1"Version of this public webhook contract.
event.occurredAtISO-8601 stringUTC timestamp for when the event occurred.
dataobjectPresent only for project.matched. webhook.test omits data entirely.
data.projectobjectSelected posting: id, title, description, descriptionFormat, skills, author, source, and details.
data.project.idstringUnique identifier of the selected source posting.
data.project.titlestringTitle of the selected source posting.
data.project.descriptionstringFull source description in the declared format.
data.project.descriptionFormat"text" | "markdown"Format used by data.project.description.
data.project.skillsstring[]Normalized project skills; [] when none are available.
data.project.authorobjectname, companyName, email, and phone. Each value can be null.
data.project.author.namestring | nullName of the source contact, or null when unavailable.
data.project.author.companyNamestring | nullCompany name from the source, or null when unavailable.
data.project.author.emailstring | nullSource contact email, or null when unavailable.
data.project.author.phonestring | nullSource contact phone number, or null when unavailable.
data.project.sourceobjectfirstSeenAt, platform, and the tracked source url.
data.project.source.firstSeenAtISO-8601 stringISO-8601 timestamp when projectfit first saw the posting.
data.project.source.platformstringName of the source platform.
data.project.source.urlstringTracked URL that redirects to the original posting.
data.project.detailsobjectlanguages, hourlyRateAmount, hourlyRateCurrency, startDate, workload, durationMonths, industry, contractType, workMode, commitmentLevels, and location. Optional values are null; lists are [].
data.project.details.languagesstring[]Required project languages; [] when none are available.
data.project.details.hourlyRateAmountnumber | nullHourly-rate amount, or null when unavailable.
data.project.details.hourlyRateCurrencystring | nullHourly-rate currency, or null when unavailable.
data.project.details.startDatestring | nullProject start date, or null when unavailable.
data.project.details.workloadstring | nullNormalized workload description, or null when unavailable.
data.project.details.durationMonthsnumber | nullProject duration in months, or null when unavailable.
data.project.details.industrystring | nullProject industry, or null when unavailable.
data.project.details.contractTypestring | nullContract type, or null when unavailable.
data.project.details.workModestring | nullRemote, hybrid, or on-site mode, or null when unavailable.
data.project.details.commitmentLevelsstring[]Normalized commitment levels; [] when none are available.
data.project.details.locationstring | nullProject location, or null when unavailable.
data.projectGroupobjectMatched group: id, projectCount, title, skills, projectfitUrl, and details.
data.projectGroup.idstringUnique identifier of the matched project group.
data.projectGroup.projectCountnumberNumber of source postings combined in the project group.
data.projectGroup.titlestringCanonical title of the project group.
data.projectGroup.skillsstring[]Normalized project skills; [] when none are available.
data.projectGroup.projectfitUrlstringURL of the matching project page on projectfit.
data.projectGroup.detailsobjectThe same normalized details shape as data.project.details.
data.projectGroup.details.languagesstring[]Required project languages; [] when none are available.
data.projectGroup.details.hourlyRateAmountnumber | nullHourly-rate amount, or null when unavailable.
data.projectGroup.details.hourlyRateCurrencystring | nullHourly-rate currency, or null when unavailable.
data.projectGroup.details.startDatestring | nullProject start date, or null when unavailable.
data.projectGroup.details.workloadstring | nullNormalized workload description, or null when unavailable.
data.projectGroup.details.durationMonthsnumber | nullProject duration in months, or null when unavailable.
data.projectGroup.details.industrystring | nullProject industry, or null when unavailable.
data.projectGroup.details.contractTypestring | nullContract type, or null when unavailable.
data.projectGroup.details.workModestring | nullRemote, hybrid, or on-site mode, or null when unavailable.
data.projectGroup.details.commitmentLevelsstring[]Normalized commitment levels; [] when none are available.
data.projectGroup.details.locationstring | nullProject location, or null when unavailable.
data.matchingobjectThe result of matching the project group against the user's profile.
data.matching.level"none" | "minimal" | "good" | "perfect"Normalized match level.
data.matching.missingSkillsstring[]Skills missing from the user's profile; [] when empty.
data.matching.analysisstring | nullPersonal match analysis, or null when unavailable.

Verify the signature

  • Read the raw UTF-8 request body without changing it. Build the signed value as timestamp + a period + rawBody.
  • Calculate the lowercase hexadecimal HMAC-SHA256 with your signing secret and prefix it with v1=.
  • Use a constant-time comparison for the expected and received signatures before parsing the JSON body.
x-projectfit-signature: v1=<lowercase hex HMAC-SHA256>

HMAC_SHA256(secret, timestamp + "." + rawBody)

Responses and retries

  • Every 2xx response is successful. projectfit ignores the response body.
  • projectfit retries network errors, timeouts, HTTP 408, 425, 429, and responses from 500 upward. Other non-2xx responses, including redirects, are permanent failures.
  • Delivery is at-least-once. Deduplicate with the stable x-projectfit-delivery ID.
  • A delivery has a maximum of three attempts with exponential backoff.

Secret and verification lifecycle

  • projectfit encrypts the endpoint and signing secret at rest.
  • The signing secret is visible only once, directly after creating the webhook.
  • To change the endpoint or signing secret, delete the webhook and connect a new one.
  • A successful test activates the newly connected webhook.
  • Return a 2xx response within 10 seconds. Redirects are not followed.
  • projectfit retries network errors, timeouts, HTTP 408, 425, 429, and 5xx responses. Each delivery has up to three attempts in total.
  • Verify the signature against the unchanged request body before parsing the JSON.

Choose where we should reach you

Set up your favorite channel and stay close to every new match.

Set up notifications