> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seamless.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Receive company and contact research results with webhooks

> Configure a webhook endpoint to receive research results asynchronously — with secret validation, Node.js and Python examples, and a polling fallback.

Webhooks let Seamless push completed research results to your server the moment enrichment finishes, without requiring you to poll for status. When research completes, the platform sends a `POST` request containing the enriched record to the HTTPS endpoint you configure. Your server validates the shared secret in the request header, processes the payload, and returns an HTTP `2xx` response to acknowledge receipt.

## Prerequisites

Before you set up webhooks:

* Authenticate with an [API key](https://docs.seamless.ai/authentication/api-keys.md) (`Token` header) or [OAuth](https://docs.seamless.ai/authentication/oauth.md) (`Authorization: Bearer`).
* Create a public HTTPS endpoint that accepts `POST` requests and is reachable from the internet.
* In your Seamless account, go to [**Settings → Webhooks**](https://login.seamless.ai/settings/webhooks), create a webhook, and select its type: **Company Research** or **Contact Research**. A webhook receives only the record type it was created for.

## Payloads

The body is the enriched record itself. There is no event-type field; the webhook's type decides which records it receives.

| Webhook type | Fires when | Body shape |
| - | - | - |
| Company Research | Company research completes | Company object — same shape as [Poll company research](https://docs.seamless.ai/api-reference/v1/company-research/poll-company-research.md) results |
| Contact Research | Contact research completes | Contact object — same shape as [Poll contact research](https://docs.seamless.ai/api-reference/v1/contact-research/poll-contact-research.md) results |

Example **Company Research** payload (fields may vary by record):

```json theme={null}
{
	"name": "Seamless.AI",
	"domain": "seamless.ai",
	"apiResearchId": "Hd3Bs6Yj0Wk5Qn2Lp9Rt7",
	"phones": "+1-650-555-0100",
	"staffCountRange": "501 - 1,000",
	"revenueRange": "$1B+",
	"linkedInProfileUrl": "https://www.linkedin.com/company/seamless-ai/"
}
```

Example **Contact Research** payload (fields may vary by record):

```json theme={null}
{
	"fullName": "Jane Doe",
	"title": "Chief Technology Officer",
	"company": "Acme Corp",
	"email": "jane.doe@acme.com",
	"apiResearchId": "zfojB7c6brQ0fIGrCvwHT",
	"contactPhone1": "+1-555-555-0199",
	"lIProfileUrl": "https://www.linkedin.com/in/janedoe/"
}
```

The `apiResearchId` field corresponds to the `requestId` from your research response. Use it to match webhook deliveries to in-flight jobs.

## Webhook setup

**1. Submit a research request**

Call [Research
companies](https://docs.seamless.ai/api-reference/v1/company-research/research-companies.md) or
[Research
contacts](https://docs.seamless.ai/api-reference/v1/contact-research/research-contacts.md) to start
enrichment. Save the `requestId` returned in the response.

**2. Wait for the platform to send the event**

When research completes, Seamless sends a `POST` request to your
configured webhook endpoint. The request body contains the enriched
record as a JSON object.

**3. Validate the webhook secret**

Read the `x-seamless-webhook-secret` header from the incoming request
and compare it to the shared secret you configured in [**Settings →
Webhooks**](https://login.seamless.ai/settings/webhooks). Reject
requests where the values do not match.

**4. Parse the request body and process the event**

Parse the JSON body and extract the enriched data. Run your application
logic — store the record, trigger a downstream action, or pass it to
another system.

**5. Return an HTTP 2xx response**

Respond with a `2xx` status code after accepting the event. Seamless
treats any other status as a delivery failure and may retry the request.

## Receiver code examples

```javascript node.js theme={null}
import express from 'express';

const app = express();
const port = process.env.PORT || 3000;
const webhookSecret = process.env.WEBHOOK_SECRET;

app.use(express.json());

app.post('/webhooks/research-results', (req, res) => {
	const receivedSecret = req.header('x-seamless-webhook-secret');

	if (!receivedSecret || receivedSecret !== webhookSecret) {
		return res.status(401).json({ message: 'Invalid webhook secret' });
	}

	if (!req.body || typeof req.body !== 'object' || Array.isArray(req.body)) {
		return res.status(400).json({ message: 'Invalid JSON payload' });
	}

	try {
		console.log('Webhook received:', JSON.stringify(req.body, null, 2));
		return res.status(200).json({ received: true });
	} catch (error) {
		console.error('Webhook processing failed:', error);
		return res.status(500).json({ message: 'Webhook processing failed' });
	}
});

app.use((error, req, res, next) => {
	if (error instanceof SyntaxError && 'body' in error) {
		return res.status(400).json({ message: 'Malformed JSON body' });
	}
	return next(error);
});

app.listen(port, () => {
	console.log(`Webhook receiver listening on port ${port}`);
});
```

```python app.py theme={null}
import os
from flask import Flask, request, jsonify

app = Flask(__name__)
webhook_secret = os.environ.get('WEBHOOK_SECRET')


@app.post('/webhooks/research-results')
def receive_webhook():
    received_secret = request.headers.get('x-seamless-webhook-secret')

    if not received_secret or received_secret != webhook_secret:
        return jsonify({'message': 'Invalid webhook secret'}), 401

    payload = request.get_json(silent=True)

    if payload is None or not isinstance(payload, dict):
        return jsonify({'message': 'Invalid JSON payload'}), 400

    try:
        print('Webhook received:', payload)
        return jsonify({'received': True}), 200
    except Exception as error:
        print('Webhook processing failed:', error)
        return jsonify({'message': 'Webhook processing failed'}), 500


if __name__ == '__main__':
    port = int(os.environ.get('PORT', 3000))
    app.run(port=port)
```

The expected success response from your endpoint is:

```json theme={null}
{ "received": true }
```

## Test your endpoint

Use this `curl` command to send a test event to your local receiver before deploying:

```bash theme={null}
curl -X POST http://localhost:3000/webhooks/research-results \
  -H "Content-Type: application/json" \
  -H "x-seamless-webhook-secret: your-shared-secret" \
  -d '{"test":true}'
```

## Use polling instead

If you prefer a request-response pattern or cannot expose a public HTTPS endpoint, use polling:

1. Submit a research request using [Research companies](https://docs.seamless.ai/api-reference/v1/company-research/research-companies.md) or [Research contacts](https://docs.seamless.ai/api-reference/v1/contact-research/research-contacts.md).
2. Save the `requestId` from the response.
3. Call [Poll company research results](https://docs.seamless.ai/api-reference/v1/company-research/poll-company-research.md) or [Poll contact research results](https://docs.seamless.ai/api-reference/v1/contact-research/poll-contact-research.md) with your `requestId` at a regular interval until the `status` field returns `done`.

## Troubleshooting

> **Your endpoint does not receive events**
>
> Confirm all of the following:
>
> * A webhook exists in [**Settings → Webhooks**](https://login.seamless.ai/settings/webhooks) for your account.
> * The webhook type is **Company Research** or **Contact Research**, depending on what you submitted.
> * Your endpoint is publicly reachable over HTTPS — local addresses such as `localhost` will not receive deliveries from the platform.

> **Your endpoint returns 401**
>
> A `401` response means the secret validation failed. Check that the value of
> the `x-seamless-webhook-secret` header in the incoming request matches your
> configured shared secret exactly, including case and whitespace. Verify that
> the secret is correctly set in your deployed environment's variables.

> **Your endpoint returns 400**
>
> A `400` response means the request body was rejected. Confirm that your
> endpoint accepts `POST` requests and that your application parses the body
> as JSON before attempting to read any fields. If you're using Express,
> ensure `express.json()` middleware runs before your route handler.

> **You want a request-response pattern instead**
>
> Use the polling endpoints. See [Poll company research results](https://docs.seamless.ai/api-reference/v1/company-research/poll-company-research.md) for companies and [Poll contact research results](https://docs.seamless.ai/api-reference/v1/contact-research/poll-contact-research.md) for contacts. Polling requires no public endpoint and works in any environment that can make outbound HTTP requests.

## Related guides

> **[Authenticate and make your first request](https://docs.seamless.ai/authentication/first-request.md)**
>
> Search, research, and poll with runnable examples.

> **[Understand identifiers and request flow](https://docs.seamless.ai/core-concepts/understand-identifiers-and-request-flow.md)**
>
> Learn which identifiers to save at each step and how the endpoint types fit
> together.

> **[Agent quickstart](https://docs.seamless.ai/api-catalog/agent-quickstart.md)**
>
> Give an AI agent the rules it needs to call the API reliably.

> **[Troubleshoot authentication and request failures](https://docs.seamless.ai/troubleshooting/authentication-and-request-failures.md)**
>
> Diagnose and resolve common errors in authentication, research submissions, and webhook delivery.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.