We built and shipped a production integration for an independent insurance agency that generates ACORD packets automatically and keeps customer and policy data in sync with their CRM and marketing stack. The system uses AMS360's official integration surfaces when available and falls back to scheduled CSV exports where APIs are gated, so daily work does not stall on manual rekeying.
Definition: AMS360 API integration is the practice of connecting Vertafore AMS360's data surfaces to external systems so customer, policy, and activity data flow into CRMs and downstream automations without manual export and rekey.
The problem it solves
Insurance teams were toggling between AMS360 eForms and a separate CRM. Certificates and updates lived in email. Marketing lists were stale because AMS360 data never flowed out cleanly. ACORD forms typed directly on eForms did not feed the underlying records, so staff retyped the same facts into the CRM.
| Manual process | Automated system |
|---|---|
| Download scheduled reports, save attachments, import CSV into CRM every week | Mailbox listener ingests AMS360 scheduled CSVs continuously and upserts records into CRM minute by minute |
| Hand fill ACORD 25/125 from scattered fields | ACORD PDFs pre-filled from canonical data, rendered to PDF, and shared via CRM links |
| Build campaigns off old lists | CRM stays current from AMS360 data so segments and journeys are always fresh |
| Chase down typos and duplicates | Deterministic keys and an idempotent upsert ledger prevent double records |
If you run AMS360 and want ACORD packets out and contacts flowing into your CRM without manual work, this guide shows exactly how the production system runs and how to build it safely.
How the automation works
We design the integration as a two-lane highway: lane one uses official AMS360 APIs under the agency's SDK agreement. Lane two uses scheduled CSV exports delivered by email for entities that are not yet exposed or while access is pending. ACORD forms are generated outside AMS360 from canonical data to avoid retyping, then attached back to the client record in the CRM.
- AMS360 integration surface: Vertafore documents a Web Service API that requires a Web Service SDK agreement and a WSAPI user with scoped permissions. Release notes also reference REST EMS endpoints. We only call documented surfaces the client is contractually enabled for. When access is not in place, we rely on scheduled CSV exports delivered by email.
- CSV ingest and normalization: AMS360 can export reports to CSV and schedule delivery to email. We ingest those attachments, validate headers, and map fields into a normalized contact and policy shape.
- Idempotent upsert to CRM: We compute stable dedup keys, write to a state ledger, and upsert to the CRM. This supports marketing segments and sales routing without duplicates.
- ACORD form generator: Since data typed directly on eForms is saved to the form only and does not flow back to customer or policy records, we generate ACORD PDFs from the canonical record and store the output with the CRM deal or contact.
- Audit and replay: All inputs are archived. Every transformation is logged with a hash so we can replay a day or a single customer safely.
Step-by-step: how to build it
1) Set up AMS360 lanes: WSAPI or Scheduled Reports
Answer first: enable the official API if you have the SDK agreement and a WSAPI user. Otherwise schedule CSV reports to an intake mailbox.
Vertafore documents that the Web Service API requires an SDK agreement and WSAPI credentials with scoped entity permissions. When the client's access was pending, we configured Scheduled Reports to send CSVs to an automation mailbox.
AMS360 setup checklist
- WSAPI: confirm SDK agreement signed. Create WSAPI user with minimal entity scope.
- CSV lane: schedule the required reports to CSV. Set frequency daily. Recipient: intake@yourdomain.com.
- Include clear column headers in exports. Avoid PDF for data feeds.Key gotcha: treat webhooks as a gap. AMS360 documentation does not confirm outbound webhooks, so design for polling or scheduled delivery.
2) Build the mailbox listener and CSV mapper
Answer first: ingest emailed CSV attachments and map them to your canonical schema.
We run a small Node service that polls IMAP, extracts CSV attachments, validates headers, and writes normalized rows to Postgres.
// imap-listener.js
import Imap from 'imap';
import { simpleParser } from 'mailparser';
import { parse } from 'csv-parse/sync';
import pg from 'pg';
const imap = new Imap({ user: process.env.IMAP_USER, password: process.env.IMAP_PASS, host: process.env.IMAP_HOST, tls: true });
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
function toCanonical(row) {
return {
external_id: row.CustomerID || row.ClientCode,
name: row.CustomerName,
email: row.PrimaryEmail?.toLowerCase() || null,
phone: row.Phone,
address: `${row.Addr1 || ''} ${row.City || ''} ${row.State || ''} ${row.Zip || ''}`.trim(),
policy_number: row.PolicyNumber || null,
line: row.LineOfBusiness || null,
effective_date: row.EffectiveDate || null
};
}
async function save(rows) {
const client = await pool.connect();
try {
await client.query('BEGIN');
for (const r of rows) {
await client.query(
`INSERT INTO intake_contacts(external_id, name, email, phone, address, policy_number, line, effective_date)
VALUES($1,$2,$3,$4,$5,$6,$7,$8)
ON CONFLICT (external_id) DO UPDATE SET name=EXCLUDED.name, email=EXCLUDED.email, phone=EXCLUDED.phone, address=EXCLUDED.address, policy_number=EXCLUDED.policy_number, line=EXCLUDED.line, effective_date=EXCLUDED.effective_date`,
[r.external_id, r.name, r.email, r.phone, r.address, r.policy_number, r.line, r.effective_date]
);
}
await client.query('COMMIT');
} finally { client.release(); }
}
imap.once('mail', () => {/* noop: IDLE keeps firing */});
imap.once('ready', () => {
imap.openBox('INBOX', false, () => {
imap.on('mail', () => {
const f = imap.seq.fetch('1:*', { bodies: '', struct: true });
f.on('message', (m) => {
m.on('body', async (stream) => {
const mail = await simpleParser(stream);
for (const att of mail.attachments || []) {
if (att.filename?.toLowerCase().endsWith('.csv')) {
const records = parse(att.content.toString('utf8'), { columns: true, skip_empty_lines: true });
const rows = records.map(toCanonical);
await save(rows);
}
}
});
});
});
});
});
imap.connect();Key gotcha: enforce header contracts per report. If AMS360 staff edit a layout, reject with a clear error and alert ops.
3) Add the WSAPI or EMS API path when enabled
Answer first: when the agency enables API access, create a second lane that pulls entities directly.
Vertafore's Web Service API is SOAP based and requires the SDK agreement and a WSAPI user. Release notes also show REST EMS endpoints for some entities. We configure the client's allowed surface and keep base URLs and auth in environment variables. Do not hardcode any endpoint names you cannot verify in the client's docs.
// soap-wsapi.js
import soap from 'soap';
export async function fetchAmsEntities(wsdlUrl, creds, params) {
const client = await soap.createClientAsync(wsdlUrl);
client.addHttpHeader('Authorization', 'Basic ' + Buffer.from(creds.user + ':' + creds.pass).toString('base64'));
// Call permitted operations only, as documented for this tenant
// Example shape: const [resp] = await client.SomeEntityGetAsync(params);
// return resp?.SomeEntityGetResult || [];
}Key gotcha: API access is not self-serve. Coordinate the SDK paperwork and WSAPI user creation with the client's AMS admin before writing any code.
4) Make upserts idempotent and CRM agnostic
Answer first: compute a deterministic dedup key and write to a ledger before touching the CRM.
We use external_id as the truth and fall back to an email plus name hash. Each upsert writes to a ledger table first, then pushes to the CRM. This lets us replay any day and prevents double-sends.
-- schema.sql
CREATE TABLE IF NOT EXISTS upsert_ledger (
key text PRIMARY KEY,
sha256 text NOT NULL,
seen_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS intake_contacts (
external_id text PRIMARY KEY,
name text, email text, phone text, address text,
policy_number text, line text, effective_date date
);// crm-upsert.js
import crypto from 'crypto';
import fetch from 'node-fetch';
import pg from 'pg';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
function dedupKey(r) {
return r.external_id || crypto.createHash('sha256').update((r.email || '') + '|' + (r.name || '')).digest('hex');
}
async function seenBefore(client, key, sha) {
const res = await client.query('SELECT sha256 FROM upsert_ledger WHERE key=$1', [key]);
if (!res.rowCount) return false;
return res.rows[0].sha256 === sha;
}
export async function upsertToCRM(row) {
const client = await pool.connect();
try {
const key = dedupKey(row);
const sha = crypto.createHash('sha256').update(JSON.stringify(row)).digest('hex');
if (await seenBefore(client, key, sha)) return { status: 'skip' };
await client.query('BEGIN');
await client.query('INSERT INTO upsert_ledger(key, sha256) VALUES($1,$2) ON CONFLICT (key) DO UPDATE SET sha256=EXCLUDED.sha256, seen_at=now()', [key, sha]);
// Replace with your CRM call. Keep provider-agnostic.
await fetch(process.env.CRM_BASE_URL + '/contacts', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.CRM_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify(row)
});
await client.query('COMMIT');
return { status: 'upsert' };
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally { client.release(); }
}Key gotcha: some CRMs throttle bursts. Batch in small chunks and back off on 429s.
5) Generate ACORD PDFs from canonical data
Answer first: fill ACORD forms from your normalized record, not from eForm typing.
Vertafore notes that data typed directly on eForms is saved to the form only and does not flow back to customer or policy records. We generate ACORD PDFs from canonical data and attach them in the CRM.
// acord-fill.js
import { PDFDocument } from 'pdf-lib';
import fs from 'fs/promises';
export async function renderAcord25(cert) {
const pdfBytes = await fs.readFile('./templates/ACORD_25.pdf');
const pdfDoc = await PDFDocument.load(pdfBytes);
const form = pdfDoc.getForm();
form.getTextField('InsuredName').setText(cert.insuredName);
form.getTextField('Addr1').setText(cert.address1);
form.getTextField('City').setText(cert.city);
form.getTextField('State').setText(cert.state);
form.getTextField('Zip').setText(cert.zip);
form.getTextField('ProducerName').setText(cert.producerName);
// ... map remaining fields from canonical policy coverage data
form.flatten();
return await pdfDoc.save();
}Key gotcha: keep templates versioned. When ACORD updates a form, a field map drift will break fills. Add a template version column to your ledger.
6) Attach outputs and route to marketing
Answer first: store the PDF and refresh segments instantly.
We upload the rendered PDFs to secure object storage and write the link to the CRM record. On the marketing side we push lifecycle flags and policy attributes as traits into journeys so that coverage change or renewal segments stay fresh.
// attach-and-traits.js
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import fetch from 'node-fetch';
const s3 = new S3Client({ region: process.env.AWS_REGION });
export async function attachPdfAndTag(contactId, pdfBytes) {
const key = `acord/${contactId}/${Date.now()}.pdf`;
await s3.send(new PutObjectCommand({ Bucket: process.env.BUCKET, Key: key, Body: pdfBytes, ContentType: 'application/pdf' }));
await fetch(`${process.env.CRM_BASE_URL}/contacts/${contactId}/files`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.CRM_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ url: `s3://${process.env.BUCKET}/${key}`, label: 'ACORD Certificate' }) });
await fetch(`${process.env.CDP_URL}/identify`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.CDP_TOKEN}` }, body: JSON.stringify({ userId: contactId, traits: { hasActivePolicy: true } }) });
}Key gotcha: store PII cautiously. Never put SSNs or full account numbers into marketing systems.
Where it gets complicated
API access is gated. Vertafore's Web Service API requires an SDK agreement and a WSAPI user with scoped permissions. Plan for a CSV lane first so value ships while paperwork runs.
Two surfaces, two behaviors. Release notes show REST EMS endpoints while WSAPI is SOAP based. We observed behavior differences in how records created by one surface affected the other. Keep each lane's assumptions isolated in code.
eForms do not back-populate. Vertafore documents that data typed directly on forms is saved to the form only and does not flow back to customer or policy records. Build from a canonical data model and treat eForms as output, not as your system of record.
No webhooks confirmed. Without documented outbound webhooks, the safe posture is scheduled email delivery or polling. Handle dedup and late-arriving updates.
Column drift and schedule edits. Scheduled Reports are editable by humans. A renamed column will silently shift a CSV. Lock headers with a contract and alert on mismatch before any write.
Segmentation and deliverability. Pushing stale or over-broad segments will burn sender reputation. Use renewal windows and coverage changes to tighten journeys.
What this actually changes
In production this removed rekeying from ACORD packet creation and kept the CRM fresh enough that marketing did not have to ask for manual list pulls. The integration is resilient to API gating because the CSV lane delivers value on day one and the API lane turns on as soon as the SDK agreement and WSAPI user are live.
One external anchor: knowledge workers spend about 20 percent of their time searching for information across tools, according to McKinsey research. Consolidating AMS360 data into a CRM reduces that hunt so reps can act faster. Source: https://www.mckinsey.com/capabilities/people-and-organizational-performance/our-insights/the-social-economy
Frequently asked questions
Does AMS360 have an official API?
Yes. Vertafore documents a Web Service API that requires a Web Service SDK agreement and a WSAPI user with scoped permissions. Recent release notes also reference REST EMS endpoints. Base URLs and auth details are tenant specific, so we configure them from the client's documentation rather than guessing.
Can I use Zapier or Make to integrate AMS360?
We did not find an official Zapier or Make connector. Vertafore offers AMS360 Connect for Salesforce as an official path. In our builds, we either use the documented API surfaces or scheduled CSV exports to bridge data into the CRM and CDP.
Can the system auto-fill ACORD forms?
Yes. We generate ACORD PDFs from the canonical data model and store the files with the CRM record. Vertafore notes that typing into eForms does not flow back to records, so we do not rely on manual entry for data integrity.
How do you prevent duplicates in the CRM?
We compute a deterministic key per record, write to an upsert ledger, then call the CRM. If the payload hash has not changed since the last write, we skip the upsert. This prevents double-sends and allows safe replays.
What if we cannot get API access approved right away?
We start with the CSV lane using AMS360 Scheduled Reports sent to a dedicated mailbox. The listener ingests attachments and upserts to the CRM. When the SDK agreement is complete and WSAPI credentials are active, we add the API lane without changing downstream contracts.
Closing this out: if you want ACORD packets and current CRM segments without rekeying, we have shipped this integration and can adapt it to your AMS360 setup. See our broader scope of work under CRM automation, read how we bridged another closed insurance system in our related post on EZLynx's Zapier gap and how to bridge it, then book a 15-minute call.
Want us to build this for you?
15-minute discovery call. No pitch. We tell you what to automate first.
Book a Discovery Call