Gareth Wilson Gareth Wilson

Guide to RocketReach Webhooks: Features and Best Practices

Published


RocketReach webhooks deliver the results of contact lookups. RocketReach finds emails, phone numbers and profile details for the people you ask about, and some of that work takes a while. Instead of polling a status endpoint until a lookup finishes, you can name a webhook in the request and RocketReach sends the result to it when it is ready. If you enrich leads or CRM records with RocketReach, webhooks are how those results reach your system.

This guide covers how RocketReach webhooks are set up and signed, what their bodies contain, the gaps in RocketReach's documentation you need to plan around, and the practices that keep a RocketReach receiver reliable.

What are RocketReach webhooks?

RocketReach webhooks aren't change notifications, and there are no event types to subscribe to. A webhook receives the result of an asynchronous lookup:

  • A People Lookup (GET /person/lookup) result arrives as one profile object.
  • A Bulk People Lookup (POST /bulkLookup) result arrives as a list of profile objects.

You create webhooks on the API Usage & Settings page and choose which lookup endpoints each one serves. A lookup request names a webhook with webhook_id. If it doesn't, RocketReach uses your top-most enabled webhook. Bulk People Lookup can only run with a webhook: it needs one enabled for that endpoint, or a webhook_id in the request.

Each delivery carries an RR-Request-ID header. The lookup request returns the same ID in its own response headers, so you can match a result to the request that started it.

RocketReach webhook features

FeatureDetails
ConfigurationAPI Usage & Settings page, one webhook per URL, with the endpoints it serves
Choosing a webhookwebhook_id on the lookup request, or the top-most enabled webhook
DeliveriesPeople Lookup results (an object) and Bulk People Lookup results (a list)
Event typesNone
CorrelationRR-Request-ID header, matching the lookup request's response
Signature headerX-RocketReach-Signature, once a secret is generated
SchemeHMAC-SHA256, base64, no prefix
Signed contentThe raw request body, nothing else
KeyThe webhook secret, used as UTF-8 text
TestingTest Webhook button on the settings page
Webhook managementEnable, disable or delete each webhook

RocketReach doesn't document a retry policy, a response timeout, the expected success code or source IPs for webhook deliveries.

Payloads

RocketReach's webhook page says a delivery "will always have the following format (or a list of these objects for bulk requests)", and shows the start of a profile:

{
  "profile_list": {
    "id": 1000,
    "name": "Example Profile List"
  },
  "id": 123456,
  ...
}

The rest of the object is the People Lookup response: the profile's name, title, employer, emails, phone numbers and links. id is the RocketReach profile ID, and profile_list is the list the profile was added to.

The People Lookup response also has a status field. Only complete means the contact information is final. RocketReach's own pages don't agree on the other values: the People Lookup schema lists progress, searching and not queued, and the status endpoint's page lists failed, waiting, searching and progress. Treat anything other than complete as unfinished or failed.

Because there is no type field, the shape is the only thing that tells a single result from a bulk one. Check whether the parsed body is an array before you read it.

Setting up RocketReach webhooks

  1. Open the API Usage & Settings page in your RocketReach account.
  2. Enter your webhook URL, check the lookup endpoints it should serve, and click Create.
  3. Make sure the webhook is enabled, then click Test Webhook to check the connection. RocketReach describes the test as "a basic JSON payload", so don't expect it to look like a lookup result. RocketReach also doesn't say whether the test is signed, so confirm verification with a real lookup.
  4. On the same webhook, click Generate Secret and wait for the confirmation. Store the secret server-side, for example as ROCKETREACH_WEBHOOK_SECRET.
  5. Pass the webhook's ID as webhook_id on your lookups:
curl "https://api.rocketreach.co/api/v2/person/lookup?id=123456&webhook_id=1004" \
  -H "Api-Key: $ROCKETREACH_API_KEY"

Store the RR-Request-ID from the response, so you can match it to the delivery.

Generating the secret is the step that turns signing on. Until a webhook has a secret, RocketReach sends its deliveries with no X-RocketReach-Signature header at all.

If you receive RocketReach webhooks through Hookdeck, create a source with the RocketReach type and use its URL as the webhook URL. Generate a webhook secret in the RocketReach API settings and paste it into the source as the webhook secret. Without one, deliveries are unsigned and fail verification.

Securing RocketReach webhooks

Read X-RocketReach-Signature, compute an HMAC-SHA256 of the raw request body with the webhook secret as the key, base64-encode the digest, and compare in constant time. Use the secret as UTF-8 text: don't base64-decode it. Base64 is case-sensitive, so compare the header exactly as sent.

const crypto = require('crypto');
const express = require('express');

function verifyRocketReachSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
  const received = Buffer.from(signatureHeader.trim(), 'utf8');
  const computed = Buffer.from(expected, 'utf8');
  return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}

const app = express();

app.post('/webhooks/rocketreach', express.raw({ type: '*/*' }), (req, res) => {
  const secret = process.env.ROCKETREACH_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500); // fail closed

  if (!verifyRocketReachSignature(req.body, req.get('x-rocketreach-signature'), secret)) {
    return res.sendStatus(401);
  }

  const body = JSON.parse(req.body.toString('utf8'));
  const profiles = Array.isArray(body) ? body : [body];
  const request_id = req.get('rr-request-id');
  // Store profiles against request_id; act on status === 'complete'.
  res.sendStatus(200);
});

The same check in Python:

import base64
import hashlib
import hmac


def verify_rocketreach_signature(raw_body: bytes, signature_header, secret: str) -> bool:
    if not signature_header or not secret:
        return False
    digest = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).digest()
    expected = base64.b64encode(digest)
    return hmac.compare_digest(signature_header.strip().encode("utf-8"), expected)


# In FastAPI: raw_body = await request.body()
# verify_rocketreach_signature(raw_body, request.headers.get("x-rocketreach-signature"),
#                              os.environ["ROCKETREACH_WEBHOOK_SECRET"])

RocketReach's Python sample passes two str values to hmac.compare_digest, which raises TypeError if the received header contains non-ASCII characters. Encoding both sides to bytes turns a malformed header into a clean 401 instead of a 500.

No timestamp is sent or signed, so the signature tells you who sent a delivery but not when.

Make RocketReach webhooks production-ready. Hookdeck Event Gateway verifies the X-RocketReach-Signature header, stores every lookup result as it arrives, and delivers it to your service with retries you control.

RocketReach webhook limitations and pain points

Signing is off until you generate a secret

The Problem: A receiver that skips verification when the header is missing accepts anything sent to its URL.

Why It Happens: A new webhook has no secret, and RocketReach only adds X-RocketReach-Signature once you click Generate Secret.

Workarounds:

  • Generate a secret for every webhook before you use it in a lookup.
  • Reject deliveries without the header rather than treating them as unsigned and valid.

How Hookdeck Can Help: Hookdeck verifies every RocketReach delivery against the secret on the source. Hookdeck answers RocketReach with a 200 either way, then records a request that fails the check, including one with no signature header, as failed verification, so it never reaches your destination.

No documented retries

The Problem: You can't tell how long your endpoint can be down before a lookup result is lost.

Why It Happens: RocketReach doesn't say whether it retries a failed delivery, how often, or what response it counts as success.

Workarounds:

  • Store the RR-Request-ID for every lookup you start, and track which ones have had a result.
  • For a missing People Lookup result, fall back to /person/checkStatus with the profile IDs.

How Hookdeck Can Help: Hookdeck accepts each delivery as soon as RocketReach sends it and stores it, then delivers it to your service with retries you configure, so a deploy or outage on your side no longer depends on RocketReach retrying.

No event type to route on

The Problem: One handler has to cope with single results, bulk lists and the test payload.

Why It Happens: RocketReach sends no event name. A single lookup delivers an object and a bulk lookup delivers an array, and the Test Webhook payload is neither.

Workarounds:

  • Check Array.isArray before reading the body.
  • Create separate webhooks for People Lookup and Bulk People Lookup, with different URL paths, and pass the matching webhook_id.

How Hookdeck Can Help: Hookdeck filters on the path or on body fields such as status can send single results, bulk results and anything else to different destinations.

Results that aren't finished

The Problem: A profile is saved with missing or unverified emails.

Why It Happens: A profile's status can be something other than complete, and RocketReach's pages list different sets of values for it.

Workarounds:

  • Only treat a profile as final when status is complete.
  • Log any other value you receive, so you learn which ones reach your webhook.

How Hookdeck Can Help: Hookdeck stores every delivery, so you can search past results by status and see which values RocketReach has actually sent.

Bulk batches are capped

The Problem: Large enrichment jobs need many requests, and each one produces its own delivery to track.

Why It Happens: Bulk People Lookup accepts between 1 and 100 queries per request.

Workarounds:

  • Split large jobs into batches of 100 and keep a record of each batch's RR-Request-ID.
  • Keep the processing behind your webhook asynchronous, so a run of bulk deliveries doesn't slow your endpoint down.

How Hookdeck Can Help: Hookdeck queues bursts of bulk deliveries and passes them on at a max delivery rate your service can handle.

Best practices

Verify against the raw body

The signature covers the exact bytes RocketReach sent, including the whole list for a bulk result. In Express, use express.raw({ type: '*/*' }). In Next.js, use await request.text(). In FastAPI, use await request.body().

Plan for secret rotation

RocketReach offers Regenerate Secret but doesn't describe an overlap period. Assume the old secret stops working straight away, and update your verifier, or the secret on your Hookdeck source, at the same time.

Make handlers idempotent

There is no event ID. Key results on RR-Request-ID plus the profile id, and include status if you act on unfinished results. Hookdeck deduplication can drop repeats before they reach you. See our guide to webhook idempotency.

Acknowledge fast and process asynchronously

Verify the signature, store the body, return 200, and update your CRM or database afterwards. See why to process webhooks asynchronously.

Conclusion

RocketReach sends People Lookup and Bulk People Lookup results to a webhook you choose with webhook_id, and signs them with X-RocketReach-Signature once you generate a secret. Generate that secret first, handle both object and list bodies, match results with RR-Request-ID, and keep a fallback to the status endpoint, because RocketReach doesn't document what happens to a delivery that fails.

Hookdeck Event Gateway sits in front of your receiver, verifies every RocketReach delivery, and holds each result until your service has processed it.

Get started with Hookdeck for free and handle RocketReach webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.