Typeform 커넥터
Tajo를 통해 Typeform을 Brevo에 연결하여 폼 응답을 자동으로 동기화하고, 대화형 폼에서 리드를 캡처하며, 설문 제출 및 퀴즈 결과를 기반으로 마케팅 자동화를 트리거하십시오.
개요
| 속성 | 값 |
|---|---|
| 플랫폼 | Typeform |
| 카테고리 | 폼 및 설문 조사 (Custom) |
| 설정 복잡도 | 쉬움 |
| 공식 통합 | 아니오 |
| 동기화 데이터 | 응답, 연락처, 이벤트, 폼 |
| 인증 방법 | OAuth 2.0 / 개인 액세스 토큰 |
기능
- 실시간 응답 동기화 - 웹훅을 통해 폼 제출을 자동으로 캡처
- 연락처 생성 - 폼 응답에서 Brevo 연락처 생성 또는 업데이트
- 리드 점수 산출 - 퀴즈 점수 및 폼 데이터를 리드 자격 부여에 활용
- Hidden Fields - 맞춤형 폼을 위해 Hidden Fields를 통해 고객 데이터 전달
- 조건부 매핑 - 응답 로직에 따라 다양한 폼 필드 매핑
- 다중 폼 지원 - 여러 typeform을 다른 Brevo 목록에 연결
사전 요구 사항
시작하기 전에 다음이 준비되어 있는지 확인하십시오.
- Typeform 계정 (웹훅용 Basic 요금제 이상)
- Typeform 계정 설정의 개인 액세스 토큰
- API 접근이 가능한 Brevo 계정
- 커넥터 권한이 있는 Tajo 계정
인증
개인 액세스 토큰
# https://admin.typeform.com/account#/section/tokens 에서 토큰 생성export TYPEFORM_ACCESS_TOKEN=tfp_your_personal_access_tokenexport TAJO_API_KEY=your_tajo_api_keyexport BREVO_API_KEY=your_brevo_api_keyOAuth 2.0
// OAuth 2.0 인증 플로const authUrl = 'https://api.typeform.com/oauth/authorize?' + new URLSearchParams({ client_id: process.env.TYPEFORM_CLIENT_ID, redirect_uri: 'https://your-app.com/callback', scope: 'forms:read responses:read webhooks:write accounts:read', state: generateState() });
// 인증 코드를 액세스 토큰으로 교환const tokenResponse = await fetch('https://api.typeform.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code: authorizationCode, client_id: process.env.TYPEFORM_CLIENT_ID, client_secret: process.env.TYPEFORM_CLIENT_SECRET, redirect_uri: 'https://your-app.com/callback' })});구성
기본 설정
connectors: typeform: enabled: true access_token: "${TYPEFORM_ACCESS_TOKEN}"
forms: - form_id: "abc123" list_id: 5 mapping: email_field: "email" name_field: "full_name" - form_id: "xyz789" list_id: 6 mapping: email_field: "work_email"
sync: responses: true contacts: true events: true
webhook: enabled: true secret: "${TYPEFORM_WEBHOOK_SECRET}"필드 매핑
field_mapping: # Typeform 필드 참조를 Brevo 속성에 매핑 email: email name: FIRSTNAME company: COMPANY phone: SMS score: LEAD_SCORE quiz_result: QUIZ_SCORE nps_rating: NPS_SCORE feedback: LAST_FEEDBACKAPI 엔드포인트
| 엔드포인트 | 메서드 | 설명 |
|---|---|---|
https://api.typeform.com/forms | GET | 모든 폼 목록 |
https://api.typeform.com/forms/{form_id} | GET | 폼 가져오기 |
https://api.typeform.com/forms | POST | 폼 생성 |
https://api.typeform.com/forms/{form_id} | PUT | 폼 업데이트 |
https://api.typeform.com/forms/{form_id}/responses | GET | 응답 가져오기 |
https://api.typeform.com/forms/{form_id}/responses | DELETE | 응답 삭제 |
https://api.typeform.com/forms/{form_id}/webhooks/{tag} | PUT | 웹훅 생성/업데이트 |
https://api.typeform.com/forms/{form_id}/webhooks/{tag} | GET | 웹훅 가져오기 |
https://api.typeform.com/forms/{form_id}/webhooks | GET | 웹훅 목록 |
코드 예제
커넥터 초기화
import { TajoClient } from '@tajo/sdk';
const tajo = new TajoClient({ apiKey: process.env.TAJO_API_KEY, brevoApiKey: process.env.BREVO_API_KEY});
await tajo.connectors.connect('typeform', { accessToken: process.env.TYPEFORM_ACCESS_TOKEN, forms: ['abc123', 'xyz789']});폼 응답 가져오기
// Responses API를 사용하여 응답 가져오기const response = await fetch( 'https://api.typeform.com/forms/abc123/responses?' + new URLSearchParams({ page_size: 25, since: '2024-01-01T00:00:00Z', completed: 'true' }), { headers: { 'Authorization': `Bearer ${process.env.TYPEFORM_ACCESS_TOKEN}` } });
const data = await response.json();
// 각 응답을 Brevo로 동기화for (const item of data.items) { const answers = item.answers; const email = answers.find(a => a.field.ref === 'email')?.email;
if (email) { await tajo.contacts.sync({ email, attributes: { FIRSTNAME: answers.find(a => a.field.ref === 'name')?.text, LEAD_SCORE: item.calculated?.score || 0 }, listIds: [5] }); }}웹훅 설정
// 실시간 응답 알림을 위한 웹훅 등록await fetch( 'https://api.typeform.com/forms/abc123/webhooks/tajo-sync', { method: 'PUT', headers: { 'Authorization': `Bearer ${process.env.TYPEFORM_ACCESS_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://api.tajo.io/webhooks/typeform', enabled: true, secret: process.env.TYPEFORM_WEBHOOK_SECRET }) });웹훅 이벤트 처리
app.post('/webhooks/typeform', async (req, res) => { // 웹훅 서명 확인 const signature = req.headers['typeform-signature']; const isValid = verifyTypeformSignature( req.rawBody, signature, process.env.TYPEFORM_WEBHOOK_SECRET );
if (!isValid) return res.status(401).send('Unauthorized');
const { form_response } = req.body;
await tajo.connectors.handleWebhook('typeform', { topic: 'form_response', payload: form_response });
res.status(200).send('OK');});속도 제한
| 엔드포인트 | 속도 제한 | 참고 |
|---|---|---|
| Create API | 2 요청/초 | 폼 생성 및 업데이트 |
| Responses API | 2 요청/초 | 응답 가져오기 |
| Webhooks API | 2 요청/초 | 웹훅 관리 |
| 모든 엔드포인트 | 120 요청/분 | 전역 속도 제한 |
응답 페이지네이션
Responses API는 요청당 최대 1,000개의 응답을 반환합니다. 대량의 응답 세트를 가져올 때는 페이지네이션을 위해 before 또는 after 커서 매개변수를 사용하십시오.
문제 해결
| 문제 | 원인 | 해결 방법 |
|---|---|---|
| 웹훅이 수신되지 않음 | 웹훅이 비활성화됨 | Typeform 대시보드에서 웹훅 활성화 |
| 응답 누락 | 필터 적용됨 | since/until 및 completed 매개변수 확인 |
| 인증 오류 401 | 토큰 만료됨 | 새 개인 액세스 토큰 생성 |
| 속도 제한 429 | 너무 많은 요청 | 요청 스로틀링 구현 |
| 답변이 비어 있음 | 선택적 필드 | null/undefined 답변 값 처리 |
디버그 모드
connectors: typeform: debug: true log_level: verbose log_webhooks: true log_responses: true모범 사례
- 웹훅 사용 - 실시간 응답 캡처를 위해 폴링보다 웹훅 선호
- 서명 검증 - 보안을 위해 항상 웹훅 서명 확인
- Hidden Fields 사용 - 폼에 알려진 고객 데이터 미리 채우기
- 필드 참조 매핑 - 필드 ID 대신 안정적인 필드
ref값 사용 - 부분 응답 처리 - 선택적 및 건너뛴 질문 고려
- 재시도 로직 설정 - 멱등성 웹훅 처리 구현
보안
- OAuth 2.0 - 범위 지정된 토큰 기반 인증
- 웹훅 서명 - SHA-256 HMAC 서명 검증
- HTTPS 전용 - 모든 API 엔드포인트에 TLS 필요
- 토큰 범위 지정 - 최소 필요한 OAuth 범위 요청
- 시크릿 관리 - 환경 변수 또는 시크릿 관리자에 토큰 저장
- GDPR 준수 - 데이터 삭제 요청에 Delete Responses API 사용