定制 john-wink/telli-laravel-wrapper 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

john-wink/telli-laravel-wrapper

Composer 安装命令:

composer require john-wink/telli-laravel-wrapper

包简介

A Laravel wrapper for the telli API v2 - AI phone call agents

README 文档

README

Latest Version on Packagist GitHub Tests Action Status Total Downloads

A Laravel wrapper for the telli API - AI phone call agents. Covers the v2 contacts/properties/agents API and the v1 calls, dialer and webhook surface. Fully typed DTOs via spatie/laravel-data, a typed exception per documented API error code, cursor pagination as lazy collections, and an idempotent contact create.

Requirements

  • PHP 8.3+
  • Laravel 12

Installation

composer require john-wink/telli-laravel-wrapper

Publish the config file (optional):

php artisan vendor:publish --tag="telli-config"

Publish the translations (optional, ships with en and de):

php artisan vendor:publish --tag="telli-translations"

Set your API key in .env:

TELLI_API_KEY=your-api-key

Usage

Contacts

use JohnWink\Telli\Facades\Telli;
use JohnWink\Telli\Data\CreateContactData;
use JohnWink\Telli\Data\UpdateContactData;
use JohnWink\Telli\Data\ContactPropertyInputData;
use Spatie\LaravelData\DataCollection;

$contact = Telli::contacts()->create(new CreateContactData(
    firstName: 'Max',
    lastName: 'Mustermann',
    phoneNumber: '+4915112345678',
    externalId: 'lead-4711',
    properties: new DataCollection(ContactPropertyInputData::class, [
        new ContactPropertyInputData(key: 'lead_score', value: 87),
    ]),
));

$page = Telli::contacts()->list(limit: 100);          // one page + pageInfo/meta
Telli::contacts()->all()->each(fn ($contact) => ...); // lazy, follows the cursor

$contact = Telli::contacts()->get($id);
$contact = Telli::contacts()->getByExternalId('lead-4711');
$contact = Telli::contacts()->update($id, new UpdateContactData(email: 'new@example.com'));
Telli::contacts()->delete($id);

Replace semantics: Telli::contacts()->replace($id, $data) issues a PUT. Optional fields you omit are reset to null (respectively [] for properties) on the server - unlike update(), which only touches the fields you send.

Idempotent create: when externalId is set and the API answers with a 5xx or the connection drops, the wrapper looks the contact up by external ID before retrying - you never create duplicates. Without an externalId there is nothing to check against, so a failed create is NOT retried; set an externalId whenever you can.

Contact properties

use JohnWink\Telli\Data\CreateContactPropertyData;
use JohnWink\Telli\Data\UpdateContactPropertyData;
use JohnWink\Telli\Enums\PropertyDataType;

$properties = Telli::contactProperties()->list();
$property = Telli::contactProperties()->create(new CreateContactPropertyData(
    key: 'lead_score',
    dataType: PropertyDataType::Number,
    label: 'Lead Score',
));
$property = Telli::contactProperties()->get('lead_score');
$property = Telli::contactProperties()->update('lead_score', new UpdateContactPropertyData(label: 'Score'));

Agents & account health

$agents = Telli::agents()->all();          // lazy collection over all pages
$agent = Telli::agents()->get($agentId);
$health = Telli::account()->health();      // AccountHealthStatus::Operational|Degraded

Calls (v1 API)

telli's call endpoints live on the v1 API (v2 does not cover calls yet); the wrapper maps them to the same camelCase PHP API:

use JohnWink\Telli\Data\Calls\ScheduleCallData;
use JohnWink\Telli\Data\Calls\ScheduleData;

$result = Telli::calls()->schedule(new ScheduleCallData(
    contactId: $contact->id,
    agentId: $agentId,
));                                            // respects the dialer window

Telli::calls()->schedule(new ScheduleCallData(
    contactId: $contact->id,
    agentId: $agentId,
    schedule: ScheduleData::for(now()->addHours(2)),
));                                            // scheduled for a specific time

$call = Telli::calls()->get($callId);          // transcript, analysis, booked_slot_for, recording
$page = Telli::calls()->list(contactId: $contact->id);

Telli::calls()->initiate(...);                 // exists, but telli recommends schedule() -
                                               // initiate() calls even outside business hours

Telli::dialer()->remove($contactId) takes a contact out of the auto-dialer loop; Telli::phoneNumbers() manages SIP numbers. A 402 response (insufficient funds) raises a typed PaymentRequiredException.

Telli::account()->verifyApiKey() returns whether the configured key is valid - handy for settings screens.

Webhooks

telli signs webhooks with Svix. Verify and parse them without extra dependencies:

use JohnWink\Telli\Facades\Telli;

Telli::webhooks()->verify(
    payload: $request->getContent(),
    svixId: $request->header('svix-id'),
    svixTimestamp: $request->header('svix-timestamp'),
    svixSignature: $request->header('svix-signature'),
    secret: $connection->webhook_secret,
);                                             // throws TelliWebhookSignatureException on mismatch

$event = Telli::webhooks()->parse($request->json()->all());

if ($call = $event->call()) {                  // call_ended events
    $call->transcript; $call->bookedSlotFor; $call->recordingUrl;
}

Multi-tenant / per-team API keys

The config key is just the default account. Scope any call to a different telli account at runtime - withApiKey() returns a NEW client and never mutates the shared singleton, so there is no key bleed between requests, queued jobs or Octane workers:

Telli::withApiKey($team->telli_api_key)->contacts()->create($data);

Error handling

Every documented telli error code maps to its own exception with typed fields, all extending TelliRequestException (which extends TelliException):

use JohnWink\Telli\Exceptions\ContactNotFoundException;
use JohnWink\Telli\Exceptions\TelliValidationException;
use JohnWink\Telli\Exceptions\TelliException;

try {
    Telli::contacts()->get($id);
} catch (ContactNotFoundException $exception) {
    $exception->contactId;      // typed payload from the API
} catch (TelliValidationException $exception) {
    $exception->issues;         // list of ValidationIssueData (code, message, path)
} catch (TelliException $exception) {
    // configuration, connection or any other API error
}

Exception messages are translated (en, de); the raw API message stays available via $exception->apiMessage, the raw code via $exception->rawCode.

Retries

429 responses are retried for every request; connection errors and 5xx responses are retried for non-POST requests only — a blind POST retry could create duplicates. Failed POST creates recover through the verify-then-retry mechanism instead (see "Idempotent create" above) when a natural key (externalId / property key) is present. The backoff is configurable (config/telli.php); a Retry-After header on 429 responses takes precedence.

Testing your integration

The wrapper uses Laravel's HTTP client, so Http::fake() works out of the box:

Http::fake(['api.telli.com/*' => Http::response([...])]);

Or swap the whole client with a mock:

use JohnWink\Telli\Facades\Telli;

Telli::swap($mock);

Changelog

See CHANGELOG.

License

MIT, see LICENSE.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-11

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固