New match? You’ll know right away.
In your browser, on Telegram, by email, or inside your own workflow.
How a match reaches you
projectfit finds a match
It fits your profile and what you’re looking for.
Browser
Telegram
Webhook
Which option fits you?
| Option | Best for | Speed | Setup |
|---|---|---|---|
Browser | Working at your computer | Instant | Easy |
Telegram | Updates wherever you are | Instant | Easy |
Webhook | Your own tools and automations | Instant | Advanced |
| Your familiar inbox | Immediate, daily or weekly | Already set up |
Browser notifications
See new matches on your computer or phone, even when projectfit isn’t open.
Open your notification settings in a supported browser and select Enable on this device.
Allow notifications when your browser asks. Permission applies only to the current browser profile and device.
Repeat setup for up to three devices. Connected devices can be removed individually.
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.
Select Connect Telegram in your notification settings.
Open the projectfit bot using the link in the dialog. The link is valid for 15 minutes.
Tap Start in Telegram.
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.
Paste the webhook URL from your tool.
Save the signing secret somewhere safe. We only show it once.
Send a test. Once it works, your webhook is live.
Endpoint requirements
- The endpoint receives
HTTPSPOSTrequests on port443. 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/jsonUser-Agent:projectfit-webhooks/1.0x-projectfit-event: event type, such asproject.matchedorwebhook.testx-projectfit-delivery: stable delivery ID; use it to deduplicate at-least-once deliveryx-projectfit-timestamp: Unixtimestampin secondsx-projectfit-signature:v1signature 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.
| Field | Type | Description |
|---|---|---|
event | object | Event metadata included in every webhook payload. |
event.id | string | Stable 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.occurredAt | ISO-8601 string | UTC timestamp for when the event occurred. |
data | object | Present only for project.matched. webhook.test omits data entirely. |
data.project | object | Selected posting: id, title, description, descriptionFormat, skills, author, source, and details. |
data.project.id | string | Unique identifier of the selected source posting. |
data.project.title | string | Title of the selected source posting. |
data.project.description | string | Full source description in the declared format. |
data.project.descriptionFormat | "text" | "markdown" | Format used by data.project.description. |
data.project.skills | string[] | Normalized project skills; [] when none are available. |
data.project.author | object | name, companyName, email, and phone. Each value can be null. |
data.project.author.name | string | null | Name of the source contact, or null when unavailable. |
data.project.author.companyName | string | null | Company name from the source, or null when unavailable. |
data.project.author.email | string | null | Source contact email, or null when unavailable. |
data.project.author.phone | string | null | Source contact phone number, or null when unavailable. |
data.project.source | object | firstSeenAt, platform, and the tracked source url. |
data.project.source.firstSeenAt | ISO-8601 string | ISO-8601 timestamp when projectfit first saw the posting. |
data.project.source.platform | string | Name of the source platform. |
data.project.source.url | string | Tracked URL that redirects to the original posting. |
data.project.details | object | languages, hourlyRateAmount, hourlyRateCurrency, startDate, workload, durationMonths, industry, contractType, workMode, commitmentLevels, and location. Optional values are null; lists are []. |
data.project.details.languages | string[] | Required project languages; [] when none are available. |
data.project.details.hourlyRateAmount | number | null | Hourly-rate amount, or null when unavailable. |
data.project.details.hourlyRateCurrency | string | null | Hourly-rate currency, or null when unavailable. |
data.project.details.startDate | string | null | Project start date, or null when unavailable. |
data.project.details.workload | string | null | Normalized workload description, or null when unavailable. |
data.project.details.durationMonths | number | null | Project duration in months, or null when unavailable. |
data.project.details.industry | string | null | Project industry, or null when unavailable. |
data.project.details.contractType | string | null | Contract type, or null when unavailable. |
data.project.details.workMode | string | null | Remote, hybrid, or on-site mode, or null when unavailable. |
data.project.details.commitmentLevels | string[] | Normalized commitment levels; [] when none are available. |
data.project.details.location | string | null | Project location, or null when unavailable. |
data.projectGroup | object | Matched group: id, projectCount, title, skills, projectfitUrl, and details. |
data.projectGroup.id | string | Unique identifier of the matched project group. |
data.projectGroup.projectCount | number | Number of source postings combined in the project group. |
data.projectGroup.title | string | Canonical title of the project group. |
data.projectGroup.skills | string[] | Normalized project skills; [] when none are available. |
data.projectGroup.projectfitUrl | string | URL of the matching project page on projectfit. |
data.projectGroup.details | object | The same normalized details shape as data.project.details. |
data.projectGroup.details.languages | string[] | Required project languages; [] when none are available. |
data.projectGroup.details.hourlyRateAmount | number | null | Hourly-rate amount, or null when unavailable. |
data.projectGroup.details.hourlyRateCurrency | string | null | Hourly-rate currency, or null when unavailable. |
data.projectGroup.details.startDate | string | null | Project start date, or null when unavailable. |
data.projectGroup.details.workload | string | null | Normalized workload description, or null when unavailable. |
data.projectGroup.details.durationMonths | number | null | Project duration in months, or null when unavailable. |
data.projectGroup.details.industry | string | null | Project industry, or null when unavailable. |
data.projectGroup.details.contractType | string | null | Contract type, or null when unavailable. |
data.projectGroup.details.workMode | string | null | Remote, hybrid, or on-site mode, or null when unavailable. |
data.projectGroup.details.commitmentLevels | string[] | Normalized commitment levels; [] when none are available. |
data.projectGroup.details.location | string | null | Project location, or null when unavailable. |
data.matching | object | The result of matching the project group against the user's profile. |
data.matching.level | "none" | "minimal" | "good" | "perfect" | Normalized match level. |
data.matching.missingSkills | string[] | Skills missing from the user's profile; [] when empty. |
data.matching.analysis | string | null | Personal match analysis, or null when unavailable. |
Verify the signature
- Read the raw
UTF-8request body without changing it. Build the signed value astimestamp+ a period +rawBody. - Calculate the lowercase hexadecimal
HMAC-SHA256with your signing secret and prefix it withv1=. - 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
2xxresponse is successful. projectfit ignores the response body. - projectfit retries network errors, timeouts,
HTTP 408,425,429, and responses from500upward. Othernon-2xxresponses, including redirects, are permanent failures. - Delivery is at-least-once. Deduplicate with the stable
x-projectfit-deliveryID. - 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