Skip to main content
When a caller asks to speak to a person, an agent with call transfer turned on hands the call over. A static transfer always goes to the same number. A webhook transfer asks your system first: Pineyard calls an HTTPS endpoint you run, sends it the details of the call, and transfers to whatever number you send back. You can use this to route by who is on shift, which branch the caller belongs to, the campaign, or the contact’s account owner.
Webhook transfer may not be available for your workspace yet. If it isn’t, the dashboard doesn’t show the Webhook option, and the API refuses transferMode: "WEBHOOK" with 400 transfer_webhook_unavailable. Contact Pineyard to enable it.

How it works

  1. During a call, the caller asks for a human.
  2. While the caller is on hold, Pineyard sends a signed POST to your URL with details of the call and contact, and the reason for the transfer.
  3. Your endpoint replies 2xx with {"to_number": "+919876543210"} before the timeout runs out.
  4. Pineyard transfers the call to that number.
If your endpoint doesn’t send back a usable number in time, the call goes to the agent’s fallback number. If the agent has no fallback number, the AI apologises and the conversation continues. There are no retries, because the caller is on hold while Pineyard waits.

Turn it on for an agent

In the dashboard

Open the agent, go to Call transfer, turn transfer on and choose Webhook. Then set:
  • Webhook URL: your endpoint.
  • Signing secret (optional, recommended): Pineyard uses it to sign every request. You can set, replace or remove it, but once saved it is never shown again.
  • Timeout: how long Pineyard waits for your answer.
  • Fallback number (optional): used when your endpoint has no answer. In webhook mode, this replaces the static transfer number.

With the API

The same settings are fields on POST /agents and PATCH /agents/{id} (scope agents.edit):
Agent responses include transferMode, transferWebhookUrl, transferWebhookTimeoutMs and hasTransferWebhookSecret. The secret itself is never returned.
Only a person signed in to the dashboard can set the URL and secret. A request made with an API key that sets or changes transferWebhookUrl or transferWebhookSecret is refused with 403 transfer_webhook_requires_user. Your URL receives caller and contact details during live calls, so a person must choose it. API keys can still read these fields and switch transferMode between STATIC and WEBHOOK. Sending back the URL the agent already has doesn’t count as a change.
Validation errors: Duplicating an agent copies its transfer settings, including the URL and the secret.

The request

The response

Reply with any 2xx status and a JSON body containing the number to transfer to:
  • to_number must be one number in E.164 format with a leading +. Pineyard doesn’t guess a country code. Don’t send a list or alternatives (such as "+919876543210, +919876543211" or "+91... or +91...").
  • The answer must arrive within the agent’s transferWebhookTimeoutMs. That budget also covers connecting to your server and Pineyard’s own processing of the call, so answer well inside it. Keep the response body small (under 16 KB).
  • Redirects aren’t followed.
Pineyard treats any of these as no one available:
  • a non-2xx status, a redirect, a timeout or a connection error
  • a body that isn’t JSON, or one with no to_number, a null or empty to_number, or a to_number that isn’t a valid number with a +
  • a to_number that contains more than one number, for example separated by ,, /, ;, |, or or and
  • a to_number that is one of the two parties already on the call (the caller or the called number)
In those cases the call goes to the agent’s fallback transferNumber if one is set. Otherwise the AI tells the caller no one is available and keeps helping them. Pineyard never retries a transfer request. If your workspace is suspended, Pineyard doesn’t call your endpoint at all. Transfers go straight to the fallback number, as if no one were available. To deliberately send a caller to the fallback number (for example, outside office hours), reply 200 {"to_number": null} or any non-2xx status.

Verify the signature

When the agent has a signing secret, X-Pineyard-Signature is v1= followed by the lowercase hex HMAC-SHA256, keyed with your secret, of:
Compute it over the raw bytes of the body, before parsing the JSON. Compare in constant time, and reject requests whose timestamp is more than 5 minutes from your clock, to block replays. The timestamp is in milliseconds.
If you don’t set a secret, requests aren’t signed and X-Pineyard-Signature isn’t sent. Set a secret for anything beyond a quick test.

Requirements

  • The URL must be https:// on port 443, with no username or password in it.
  • It must be reachable from the public internet. Pineyard won’t connect to private, loopback or link-local addresses, including through DNS. A URL that resolves to one of these is treated as no one available.
  • Your endpoint must answer well within the timeout (500 to 4000 ms, 3000 ms by default), which also covers connecting and Pineyard’s own processing. Look up your routing data ahead of time or keep it cached, and don’t call slow services before replying.

Testing

  • Set a fallback number before going live, so a slow or failing endpoint still reaches a person.
  • Use a test call or a web session from the dashboard, and ask the agent to speak to a person.
  • Log the raw body and headers of the first few requests, and check that your signature check accepts them before you start rejecting unsigned or invalid requests.
  • To test the fallback, temporarily return 503 or {"to_number": null}: the call should go to the fallback number, or, without one, the agent should apologise and carry on.
  • A tunnelling tool that gives you a public https:// URL works for local development. Make sure it answers well within the timeout.