CollectSmart Invites

Send invites from any app with a custom webhook

Post a customer's email from your own backend, Zapier, Make or n8n to a custom webhook, and ReTestimonial emails them a testimonial request.

A custom webhook lets any tool that can send an HTTP request ask your customers for a testimonial: your own backend, Zapier, Make, n8n or anything else. Your app sends one request per customer, with their email address, and ReTestimonial adds them to your invite emails. Smart Invites are on Premium and Business.

Every request that passes the webhook's checks is a real invite: it uses one invite and emails the address in it. A custom webhook has no filter until you add one, so any request with an email address invites that person.

Create a custom webhook

Follow Send invites automatically with a webhook. At Where do your customers buy?, choose Custom webhook under Something else. The paths start as email, name and company, so if your app sends those field names you don't need to change them.

The Map data step for a Custom webhook: Email path email, Name path name and Company path company with the values found in the Sample event, and Only invite when (optional) left empty

When the webhook is created, click Copy next to Your webhook URL. You'll paste it into your app.

Send a request from your app

Send a POST request to the webhook URL with the customer's details. Use your own email address for the first test, and see the request format below.

Check that it arrived

The page you created the webhook on shows Listening for the first event… until your request arrives, then Event received — it works, or why the event was skipped or failed. Later, the webhook's Activity tab lists its recent events.

The request

  • Method: POST to the webhook URL.
  • Body: one JSON object per request, for one customer. Send Content-Type: application/json. Form fields (application/x-www-form-urlencoded) work too.
  • Not accepted: a list of events, a bare value or an empty body. These get a 400 and invite no one.

A minimal body:

{
  "email": "jo@example.com",
  "name": "Jo Brown",
  "company": "Uplift Inc."
}

The same request with curl:

curl -X POST "YOUR_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{"email": "jo@example.com", "name": "Jo Brown", "company": "Uplift Inc."}'

The Idempotency-Key header is optional; see Retries and duplicates.

Map your fields

Your body doesn't have to use our field names. When you create the webhook, or later in its Settings, point each field at the place it lives in your body:

  • Email path is required. The address is lowercased and must look like an email address, or the event fails.
  • Name path and Company path are optional.

A path follows nested fields with dots, such as customer.email, and [0] picks the first item of a list, as in contacts[0].email. For form fields, the path is the field name exactly as sent.

Fields you don't map can still decide who is invited. For example, with "plan": "pro" in the body, set Only invite when (optional) to the Field path plan, equals, Value pro. Values are compared as text; > and < compare them as numbers. On Business, Conditional routing can also send different customers to different forms.

To try a body without inviting anyone, paste it on the webhook's Test tab and click Run test. See Test a Smart Invites webhook.

Signing

A custom webhook has no signing secret, and no signature header is checked. The secret part at the end of the URL is what proves a request comes from you, so treat the URL like a password:

  • Send it from your server or automation tool, never from a web page or app your customers can inspect.
  • Keep it out of public code and logs.

A webhook's URL can't be changed. If it leaks, delete the webhook and create a new one; the old URL stops working at once.

Responses

The webhook answers every request with a status code and a JSON body.

StatusBodyWhat it means
200"status": "processed" and a contactIdThe customer was added and their emails are scheduled.
200"status": "skipped" and a reasonThe request arrived fine, but nobody was invited. See the reasons below.
200"status": "error" and an errorThe request can't be used as sent, for example no email at the mapped path, an invalid address, no invites left, or a form that's paused or a draft. Sending it again won't help until you fix the cause.
200"error": "Webhook is paused"The webhook is paused. The request is discarded, and that customer isn't invited later.
400"error": "Invalid JSON body"The body isn't a single JSON object or form fields.
401"error": "Unauthorized"The URL is wrong. Copy it again from the webhook's Setup tab.
404"error": "Not found"No webhook has this URL, for example because it was deleted.
429"error": "Rate limit exceeded" and retryAfterMore than 100 requests reached this webhook in a minute. Send again after retryAfter.
500"status": "error" or "error": "Internal server error"Something went wrong on our side. Send the same request again later.

A skipped response names the reason:

  • Filter condition not matched: the body didn't meet Only invite when (optional).
  • Email suppressed (…): the address is on your do-not-email list, for example after an unsubscribe or a bounce.
  • Within N-day cooldown: someone with this address was invited in this project within the Re-invite cooldown (days), by any webhook, a file or by hand.
  • Email blocked (bounced or complained): an email to this address bounced or was reported as spam, in any project, so ReTestimonial never emails it.
  • Duplicate event (idempotency): an earlier request with the same body (and the same Idempotency-Key, if you send one) already invited a customer.

Skipped requests use no invites, and neither do the errors above.

Retries and duplicates

ReTestimonial doesn't fetch anything or retry for you. If a request gets a 429 or a 500, or no answer at all, your app should send it again. Don't resend a 200 automatically: a processed one is done, and a skipped or error one gets the same answer until you fix its cause. Once you have, resend it (see below).

A request is a duplicate when its body is exactly the same as an earlier one to this webhook that invited a customer. With an Idempotency-Key (or X-Idempotency-Key) header, it's a duplicate only when both the key and the body match. So:

  • Give each real event its own key, such as your order ID, and resend a retry with the same key and exactly the same body. A body with a new timestamp in it is a new event.
  • Duplicates are recognized for at least 30 days after the first request.
  • A request that was skipped or failed is processed again when you send it again. After you fix the cause, such as the Email path, resend it as it was; its line in Activity shows the new result.

The Re-invite cooldown (days) is a second safety net: even a request that isn't a duplicate doesn't invite the same address twice within it. At 0 there's no cooldown, so only duplicate detection stops a repeat.

Send from Zapier, Make or n8n

Each tool has a step that sends an HTTP request. Put your webhook URL in it and send the customer's details as JSON.

Zapier

Add an action with Webhooks by Zapier and pick POST as the Event. Set Payload Type to JSON, paste the webhook URL into URL, and add email, name and company in Data with values from earlier steps. If you leave Data blank, Zapier sends every field from the previous step instead, and your paths need to match those field names. An Idempotency-Key goes in Headers. See Zapier's guide, Send webhooks in Zaps.

Make

Add the HTTP app's Make a request module. Paste the webhook URL into URL, set Method to POST and Body content type to application/JSON, then fill in the body. An Idempotency-Key goes in Headers. See Make's HTTP app documentation.

n8n

Add an HTTP Request node. Set Method to POST, paste the webhook URL into URL, turn on Send Body, choose JSON as the Body Content Type and fill in the body. To add an Idempotency-Key, turn on Send Headers. See n8n's HTTP Request node documentation.

Next steps

Was this page helpful?

On this page