Gateman
Documentation/Removing Gateman

Removing Gateman

Our No lock-in guarantee

Gateman is designed to make working with external services easier, not to make your application dependent on us.

Wherever possible, Gateman uses the provider's native request formats rather than proprietary abstractions. Your provider accounts, credentials, and resources remain yours.

If you decide to stop using Gateman, you can connect directly to your providers through their APIs or SDKs. You will need to replace Gateman's authentication and any services it was performing for you, but you will not be locked into a Gateman-specific provider integration.

The examples below show direct provider calls in PHP, Node.js, Python, and Go. They use placeholders and are real provider requests when you run them. This verification checks syntax, request shape, and error handling without sending any live provider requests. Some integrations reuse native parameters directly; others require provider-specific mapping.

Replacing Gateman with direct provider access

The common change is to replace the signed Gateman request with the provider's own authentication and API or SDK call. Supply credentials through the secure configuration mechanism already chosen for your application, handle the provider's native response and errors, and replace any Gateman-specific capture, accounting, idempotency, tracking, or secret-retrieval behavior that your application still needs.

Gateman application environments are not the same thing as provider production, test, or sandbox modes. Choose the provider mode explicitly when you move the call into your application.

Payments

Stripe — stripe:use and POST /stripe/request

Gateman sends the nested native Stripe request after adding authentication and selecting the configured environment. Direct access keeps the /v1/ path, URL-encoded parameters, and Stripe Idempotency-Key; only the transport and credential handling change.

The examples create a PaymentIntent when run. Configure STRIPE_SECRET_KEY only when adapting an example for a Stripe test account.

PHP

$ch = curl_init('https://api.stripe.com/v1/payment_intents');
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
    CURLOPT_USERPWD => getenv('STRIPE_SECRET_KEY') . ':',
    CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded', 'Idempotency-Key: order-123'],
    CURLOPT_POSTFIELDS => http_build_query(['amount' => 1000, 'currency' => 'usd'], '', '&', PHP_QUERY_RFC3986)]);
$response = curl_exec($ch);
if ($response === false || curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) throw new RuntimeException('Stripe request failed');
echo $response;

Node.js

const response = await fetch('https://api.stripe.com/v1/payment_intents', {
  method: 'POST', headers: { Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`, 'Content-Type': 'application/x-www-form-urlencoded', 'Idempotency-Key': 'order-123' },
  body: new URLSearchParams({ amount: '1000', currency: 'usd' })
});
if (!response.ok) throw new Error(`Stripe HTTP ${response.status}`);
console.log(await response.json());

Python

import os, urllib.request, urllib.parse, base64, json
body = urllib.parse.urlencode({'amount': '1000', 'currency': 'usd'}).encode()
request = urllib.request.Request('https://api.stripe.com/v1/payment_intents', data=body, method='POST', headers={
    'Authorization': 'Basic ' + base64.b64encode((os.environ['STRIPE_SECRET_KEY'] + ':').encode()).decode(),
    'Content-Type': 'application/x-www-form-urlencoded', 'Idempotency-Key': 'order-123'})
with urllib.request.urlopen(request) as response:
    print(json.load(response))

Go

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
)

func main() {
	form := url.Values{"amount": {"1000"}, "currency": {"usd"}}
	req, err := http.NewRequest("POST", "https://api.stripe.com/v1/payment_intents", strings.NewReader(form.Encode()))
	if err != nil {
		panic(err)
	}
	req.SetBasicAuth(os.Getenv("STRIPE_SECRET_KEY"), "")
	req.Header.Set("Idempotency-Key", "order-123")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("Stripe HTTP %s", res.Status))
	}
	b, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

Stripe's API version is selected by the account/request headers and SDK configuration, not by Gateman's environment label. Stripe documents PaymentIntents and idempotent requests.

Square — square:use and POST /square/request

This read-only example lists locations. Direct access uses the Square access token and the production or sandbox origin selected by the application.

PHP

$ch = curl_init('https://connect.squareupsandbox.com/v2/locations');
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SQUARE_ACCESS_TOKEN_TEST'), 'Square-Version: 2026-09-16']]);
$response = curl_exec($ch); if ($response === false || curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) throw new RuntimeException('Square request failed'); echo $response;

Node.js

const response = await fetch('https://connect.squareupsandbox.com/v2/locations', { headers: { Authorization: `Bearer ${process.env.SQUARE_ACCESS_TOKEN_TEST}`, 'Square-Version': '2026-09-16' } });
if (!response.ok) throw new Error(`Square HTTP ${response.status}`); console.log(await response.json());

Python

import os, urllib.request, json
request = urllib.request.Request('https://connect.squareupsandbox.com/v2/locations', headers={'Authorization': 'Bearer ' + os.environ['SQUARE_ACCESS_TOKEN_TEST'], 'Square-Version': '2026-09-16'})
with urllib.request.urlopen(request) as response:
    if response.status >= 400: raise RuntimeError(f'Square HTTP {response.status}')
    print(json.load(response))

Go

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://connect.squareupsandbox.com/v2/locations", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("SQUARE_ACCESS_TOKEN_TEST"))
	req.Header.Set("Square-Version", "2026-09-16")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("Square HTTP %s", res.Status))
	}
	b, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

See Square's API reference and SDK documentation.

PayPal — paypal:use and POST /paypal/request

PayPal is not authenticated like Stripe or Square. Direct access first obtains an OAuth 2.0 access token, then calls a native PayPal REST endpoint. The PayPal-Request-Id header is the provider's idempotency mechanism for supported mutations.

The following examples obtain a token and create a sandbox Orders v2 request shape. They are examples only and must not be run against a live account.

PHP

$id = getenv('PAYPAL_CLIENT_ID'); $secret = getenv('PAYPAL_CLIENT_SECRET');
$token = curl_init('https://api-m.sandbox.paypal.com/v1/oauth2/token'); curl_setopt_array($token, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_USERPWD => "$id:$secret", CURLOPT_HTTPHEADER => ['Accept: application/json', 'Content-Type: application/x-www-form-urlencoded'], CURLOPT_POSTFIELDS => 'grant_type=client_credentials']); $tokenResponse=curl_exec($token); if ($tokenResponse===false || curl_getinfo($token,CURLINFO_RESPONSE_CODE)>=400) throw new RuntimeException('PayPal OAuth request failed'); $tokenBody = json_decode($tokenResponse, true, 512, JSON_THROW_ON_ERROR);
$body = json_encode(['intent'=>'CAPTURE','purchase_units'=>[['amount'=>['currency_code'=>'USD','value'=>'10.00']]]], JSON_THROW_ON_ERROR); $ch = curl_init('https://api-m.sandbox.paypal.com/v2/checkout/orders'); curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$tokenBody['access_token'],'Content-Type: application/json','PayPal-Request-Id: example-order-123'],CURLOPT_POSTFIELDS=>$body]); $response=curl_exec($ch); if ($response===false || curl_getinfo($ch,CURLINFO_RESPONSE_CODE)>=400) throw new RuntimeException('PayPal Orders request failed'); echo $response;

Node.js

const basic = Buffer.from(`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`).toString('base64');
const tokenResponse = await fetch('https://api-m.sandbox.paypal.com/v1/oauth2/token', { method: 'POST', headers: { Authorization: `Basic ${basic}`, 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'grant_type=client_credentials' }); if (!tokenResponse.ok) throw new Error(`PayPal OAuth HTTP ${tokenResponse.status}`); const token = await tokenResponse.json();
const response = await fetch('https://api-m.sandbox.paypal.com/v2/checkout/orders', { method: 'POST', headers: { Authorization: `Bearer ${token.access_token}`, 'Content-Type': 'application/json', 'PayPal-Request-Id': 'example-order-123' }, body: JSON.stringify({ intent: 'CAPTURE', purchase_units: [{ amount: { currency_code: 'USD', value: '10.00' } }] }) }); if (!response.ok) throw new Error(`PayPal Orders HTTP ${response.status}`); console.log(await response.json());

Python

import os, urllib.request, urllib.parse, urllib.error, base64, json
auth = base64.b64encode(f"{os.environ['PAYPAL_CLIENT_ID']}:{os.environ['PAYPAL_CLIENT_SECRET']}".encode()).decode()
token_req = urllib.request.Request('https://api-m.sandbox.paypal.com/v1/oauth2/token', data=b'grant_type=client_credentials', method='POST', headers={'Authorization':'Basic '+auth,'Content-Type':'application/x-www-form-urlencoded'})
with urllib.request.urlopen(token_req) as response: token = json.load(response)['access_token']
body = json.dumps({'intent':'CAPTURE','purchase_units':[{'amount':{'currency_code':'USD','value':'10.00'}}]}).encode(); req = urllib.request.Request('https://api-m.sandbox.paypal.com/v2/checkout/orders', data=body, method='POST', headers={'Authorization':'Bearer '+token,'Content-Type':'application/json','PayPal-Request-Id':'example-order-123'})
with urllib.request.urlopen(req) as response:
    if response.status >= 400: raise RuntimeError(f'PayPal Orders HTTP {response.status}')
    print(json.load(response))

Go

package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
)

func main() {
	basic := base64.StdEncoding.EncodeToString([]byte(os.Getenv("PAYPAL_CLIENT_ID") + ":" + os.Getenv("PAYPAL_CLIENT_SECRET")))
	tokenReq, err := http.NewRequest("POST", "https://api-m.sandbox.paypal.com/v1/oauth2/token", strings.NewReader(url.Values{"grant_type": {"client_credentials"}}.Encode()))
	if err != nil {
		panic(err)
	}
	tokenReq.Header.Set("Authorization", "Basic "+basic)
	tokenReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	tokenRes, err := http.DefaultClient.Do(tokenReq)
	if err != nil {
		panic(err)
	}
	defer tokenRes.Body.Close()
	if tokenRes.StatusCode >= 400 {
		panic(fmt.Sprintf("PayPal OAuth HTTP %s", tokenRes.Status))
	}
	var token struct {
		AccessToken string `json:"access_token"`
	}
	if err := json.NewDecoder(tokenRes.Body).Decode(&token); err != nil {
		panic(err)
	}
	body, err := json.Marshal(map[string]any{"intent": "CAPTURE", "purchase_units": []any{map[string]any{"amount": map[string]string{"currency_code": "USD", "value": "10.00"}}}})
	if err != nil {
		panic(err)
	}
	orderReq, err := http.NewRequest("POST", "https://api-m.sandbox.paypal.com/v2/checkout/orders", bytes.NewReader(body))
	if err != nil {
		panic(err)
	}
	orderReq.Header.Set("Authorization", "Bearer "+token.AccessToken)
	orderReq.Header.Set("Content-Type", "application/json")
	orderReq.Header.Set("PayPal-Request-Id", "example-order-123")
	res, err := http.DefaultClient.Do(orderReq)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("PayPal Orders HTTP %s", res.Status))
	}
	out, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(out))
}

PayPal's OAuth documentation, Orders API, and request-id guidance are authoritative for the complete flow.

Email

Gateman's email.send routes accept a common mail intent but map it to each provider. Production sends contact the provider; staging and development use Gateman capture. Direct integrations must choose their own test mode and handle any Gateman-specific ledger, suppression, tracking, or idempotency behavior they still require.

Amazon SES — POST /send/ses

This uses the SES API v2 SendEmail operation through each provider's native HTTP interface. The sender identity must be verified in SES. Attachments and raw MIME messages require additional SES structure and are intentionally not included in this minimal example.

PHP

$body = json_encode(['FromEmailAddress'=>getenv('SES_FROM_EMAIL'),'Destination'=>['ToAddresses'=>['recipient@example.test']],'Content'=>['Simple'=>['Subject'=>['Data'=>'Test subject'],'Body'=>['Text'=>['Data'=>'Test body']]]]], JSON_THROW_ON_ERROR);
require 'vendor/autoload.php'; $client=new Aws\SesV2\SesV2Client(['version'=>'latest','region'=>getenv('AWS_REGION')]); $result=$client->sendEmail(json_decode($body,true,512,JSON_THROW_ON_ERROR)); echo $result->get('MessageId');

Node.js

import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2'; const result=await new SESv2Client({region:process.env.AWS_REGION}).send(new SendEmailCommand({FromEmailAddress:process.env.SES_FROM_EMAIL,Destination:{ToAddresses:['recipient@example.test']},Content:{Simple:{Subject:{Data:'Test subject'},Body:{Text:{Data:'Test body'}}}}})); console.log(result.MessageId);

Python

import boto3, os
client = boto3.client('sesv2', region_name=os.environ['AWS_REGION'])
result = client.send_email(FromEmailAddress=os.environ['SES_FROM_EMAIL'], Destination={'ToAddresses':['recipient@example.test']}, Content={'Simple': {'Subject': {'Data':'Test subject'}, 'Body': {'Text': {'Data':'Test body'}}}})
print(result['MessageId'])

Go

package main

import (
	"context"
	"fmt"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/sesv2"
	"github.com/aws/aws-sdk-go-v2/service/sesv2/types"
	"os"
)

func main() {
	cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(os.Getenv("AWS_REGION")))
	if err != nil {
		panic(err)
	}
	out, err := sesv2.NewFromConfig(cfg).SendEmail(context.Background(), &sesv2.SendEmailInput{FromEmailAddress: &[]string{os.Getenv("SES_FROM_EMAIL")}[0], Destination: &types.Destination{ToAddresses: []string{"recipient@example.test"}}, Content: &types.EmailContent{Simple: &types.Message{Subject: &types.Content{Data: &[]string{"Test subject"}[0]}, Body: &types.Body{Text: &types.Content{Data: &[]string{"Test body"}[0]}}}}})
	if err != nil {
		panic(err)
	}
	fmt.Println(*out.MessageId)
}

All four blocks use the official SES v2 SDK; the SDK performs the provider's authenticated request. See SES SendEmail and SES attachments.

Postmark — POST /send/postmark

Postmark uses X-Postmark-Server-Token and its native field names. The API returns MessageID.

PHP

$body=json_encode(['From'=>getenv('POSTMARK_FROM'),'To'=>'recipient@example.test','Subject'=>'Test subject','TextBody'=>'Test body'],JSON_THROW_ON_ERROR); $ch=curl_init('https://api.postmarkapp.com/email'); curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_HTTPHEADER=>['Accept: application/json','Content-Type: application/json','X-Postmark-Server-Token: '.getenv('POSTMARK_SERVER_TOKEN')],CURLOPT_POSTFIELDS=>$body]); $response=curl_exec($ch); if ($response===false || curl_getinfo($ch,CURLINFO_RESPONSE_CODE)>=400) throw new RuntimeException('Postmark request failed'); echo $response;

Node.js

const response = await fetch('https://api.postmarkapp.com/email', { method:'POST', headers:{Accept:'application/json','Content-Type':'application/json','X-Postmark-Server-Token':process.env.POSTMARK_SERVER_TOKEN}, body:JSON.stringify({From:process.env.POSTMARK_FROM,To:'recipient@example.test',Subject:'Test subject',TextBody:'Test body'}) }); if (!response.ok) throw new Error(`Postmark HTTP ${response.status}`); console.log(await response.json());

Python

import os, urllib.request, json
body=json.dumps({'From':os.environ['POSTMARK_FROM'],'To':'recipient@example.test','Subject':'Test subject','TextBody':'Test body'}).encode(); req=urllib.request.Request('https://api.postmarkapp.com/email',data=body,method='POST',headers={'Accept':'application/json','Content-Type':'application/json','X-Postmark-Server-Token':os.environ['POSTMARK_SERVER_TOKEN']});
with urllib.request.urlopen(req) as response:
    if response.status >= 400: raise RuntimeError(f'Postmark HTTP {response.status}')
    print(json.load(response)['MessageID'])

Go

package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	body := []byte(`{"From":"` + os.Getenv("POSTMARK_FROM") + `","To":"recipient@example.test","Subject":"Test subject","TextBody":"Test body"}`)
	req, err := http.NewRequest("POST", "https://api.postmarkapp.com/email", bytes.NewReader(body))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Postmark-Server-Token", os.Getenv("POSTMARK_SERVER_TOKEN"))
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("Postmark HTTP %s", res.Status))
	}
	b, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

Official reference: Postmark Email API.

Resend — POST /send/resend

Resend uses bearer authentication and a native JSON body. The response contains the provider email identifier.

PHP

$body=json_encode(['from'=>getenv('RESEND_FROM'),'to'=>['recipient@example.test'],'subject'=>'Test subject','text'=>'Test body'],JSON_THROW_ON_ERROR); $ch=curl_init('https://api.resend.com/emails'); curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_HTTPHEADER=>['Authorization: Bearer '.getenv('RESEND_API_KEY'),'Content-Type: application/json'],CURLOPT_POSTFIELDS=>$body]); $response=curl_exec($ch); if ($response===false || curl_getinfo($ch,CURLINFO_RESPONSE_CODE)>=400) throw new RuntimeException('Resend request failed'); echo $response;

Node.js

const response=await fetch('https://api.resend.com/emails',{method:'POST',headers:{Authorization:`Bearer ${process.env.RESEND_API_KEY}`,'Content-Type':'application/json'},body:JSON.stringify({from:process.env.RESEND_FROM,to:['recipient@example.test'],subject:'Test subject',text:'Test body'})}); if (!response.ok) throw new Error(`Resend HTTP ${response.status}`); console.log(await response.json());

Python

import os, urllib.request, json
body=json.dumps({'from':os.environ['RESEND_FROM'],'to':['recipient@example.test'],'subject':'Test subject','text':'Test body'}).encode(); req=urllib.request.Request('https://api.resend.com/emails',data=body,method='POST',headers={'Authorization':'Bearer '+os.environ['RESEND_API_KEY'],'Content-Type':'application/json'});
with urllib.request.urlopen(req) as response:
    if response.status >= 400: raise RuntimeError(f'Resend HTTP {response.status}')
    print(json.load(response))

Go

package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	body := []byte(`{"from":"` + os.Getenv("RESEND_FROM") + `","to":["recipient@example.test"],"subject":"Test subject","text":"Test body"}`)
	req, err := http.NewRequest("POST", "https://api.resend.com/emails", bytes.NewReader(body))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("RESEND_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("Resend HTTP %s", res.Status))
	}
	b, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

Official reference: Resend Email API.

SMS

Direct SMS integrations use each provider's own sender/origination rules, responses, retry behavior, and test environment. Gateman's staging/development capture and capability-specific idempotency do not automatically follow the application.

Twilio — POST /twilio/sms/send

Twilio's native Messages fields remain recognizable. The direct request uses HTTP Basic authentication with the account/API-key credentials.

PHP

$ch=curl_init('https://api.twilio.com/2010-04-01/Accounts/'.rawurlencode(getenv('TWILIO_ACCOUNT_SID')).'/Messages.json'); curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_USERPWD=>getenv('TWILIO_API_KEY_SID').':'.getenv('TWILIO_API_KEY_SECRET'),CURLOPT_POSTFIELDS=>http_build_query(['To'=>'+15558675310','From'=>'+15557122661','Body'=>'Example message'],'','&',PHP_QUERY_RFC3986)]); $response=curl_exec($ch); if ($response===false || curl_getinfo($ch,CURLINFO_RESPONSE_CODE)>=400) throw new RuntimeException('Twilio request failed'); echo $response;

Node.js

const auth=Buffer.from(`${process.env.TWILIO_API_KEY_SID}:${process.env.TWILIO_API_KEY_SECRET}`).toString('base64'); const response=await fetch(`https://api.twilio.com/2010-04-01/Accounts/${process.env.TWILIO_ACCOUNT_SID}/Messages.json`,{method:'POST',headers:{Authorization:`Basic ${auth}`,'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({To:'+15558675310',From:'+15557122661',Body:'Example message'})}); if (!response.ok) throw new Error(`Twilio HTTP ${response.status}`); console.log(await response.json());

Python

import os, urllib.request, urllib.parse, base64, json
body=urllib.parse.urlencode({'To':'+15558675310','From':'+15557122661','Body':'Example message'}).encode(); auth=base64.b64encode((os.environ['TWILIO_API_KEY_SID']+':'+os.environ['TWILIO_API_KEY_SECRET']).encode()).decode(); req=urllib.request.Request('https://api.twilio.com/2010-04-01/Accounts/'+os.environ['TWILIO_ACCOUNT_SID']+'/Messages.json',data=body,method='POST',headers={'Authorization':'Basic '+auth,'Content-Type':'application/x-www-form-urlencoded'});
with urllib.request.urlopen(req) as response:
    if response.status >= 400: raise RuntimeError(f'Twilio HTTP {response.status}')
    print(json.load(response))

Go

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
)

func main() {
	form := url.Values{"To": {"+15558675310"}, "From": {"+15557122661"}, "Body": {"Example message"}}
	req, err := http.NewRequest("POST", "https://api.twilio.com/2010-04-01/Accounts/"+os.Getenv("TWILIO_ACCOUNT_SID")+"/Messages.json", strings.NewReader(form.Encode()))
	if err != nil {
		panic(err)
	}
	req.SetBasicAuth(os.Getenv("TWILIO_API_KEY_SID"), os.Getenv("TWILIO_API_KEY_SECRET"))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("Twilio HTTP %s", res.Status))
	}
	b, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

See Twilio's Messages API.

Telnyx — POST /telnyx/sms/send

Telnyx uses a native JSON message at https://api.telnyx.com/v2/messages.

PHP

$body=json_encode(['to'=>'+15558675310','messaging_profile_id'=>getenv('TELNYX_MESSAGING_PROFILE_ID'),'text'=>'Example message'],JSON_THROW_ON_ERROR); $ch=curl_init('https://api.telnyx.com/v2/messages'); curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_HTTPHEADER=>['Authorization: Bearer '.getenv('TELNYX_API_KEY'),'Content-Type: application/json'],CURLOPT_POSTFIELDS=>$body]); $response=curl_exec($ch); if ($response===false || curl_getinfo($ch,CURLINFO_RESPONSE_CODE)>=400) throw new RuntimeException('Telnyx request failed'); echo $response;

Node.js

const response=await fetch('https://api.telnyx.com/v2/messages',{method:'POST',headers:{Authorization:`Bearer ${process.env.TELNYX_API_KEY}`,'Content-Type':'application/json'},body:JSON.stringify({to:'+15558675310',messaging_profile_id:process.env.TELNYX_MESSAGING_PROFILE_ID,text:'Example message'})}); if (!response.ok) throw new Error(`Telnyx HTTP ${response.status}`); console.log(await response.json());

Python

import os, urllib.request, json
body=json.dumps({'to':'+15558675310','messaging_profile_id':os.environ['TELNYX_MESSAGING_PROFILE_ID'],'text':'Example message'}).encode(); req=urllib.request.Request('https://api.telnyx.com/v2/messages',data=body,method='POST',headers={'Authorization':'Bearer '+os.environ['TELNYX_API_KEY'],'Content-Type':'application/json'});
with urllib.request.urlopen(req) as response:
    if response.status >= 400: raise RuntimeError(f'Telnyx HTTP {response.status}')
    print(json.load(response))

Go

package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	body := []byte(`{"to":"+15558675310","messaging_profile_id":"` + os.Getenv("TELNYX_MESSAGING_PROFILE_ID") + `","text":"Example message"}`)
	req, err := http.NewRequest("POST", "https://api.telnyx.com/v2/messages", bytes.NewReader(body))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("TELNYX_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("Telnyx HTTP %s", res.Status))
	}
	b, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

See Telnyx's messaging documentation.

AWS End User Messaging SMS — POST /aws/sms/send

Gateman calls the AWS SendTextMessage operation. Direct use requires AWS credentials, a region, an origination identity or pool, and the AWS SDK's SigV4 handling.

PHP

require 'vendor/autoload.php'; use Aws\PinpointSMSVoiceV2\PinpointSMSVoiceV2Client;
$client=new PinpointSMSVoiceV2Client(['version'=>'2022-03-31','region'=>getenv('AWS_REGION')]); $result=$client->sendTextMessage(['DestinationPhoneNumber'=>'+15558675310','OriginationIdentity'=>getenv('AWS_ORIGINATION_IDENTITY'),'MessageBody'=>'Example message','MessageType'=>'TRANSACTIONAL']); echo $result->get('MessageId');

Node.js

import { PinpointSMSVoiceV2Client, SendTextMessageCommand } from '@aws-sdk/client-pinpoint-sms-voice-v2';
const client=new PinpointSMSVoiceV2Client({region:process.env.AWS_REGION}); const result=await client.send(new SendTextMessageCommand({DestinationPhoneNumber:'+15558675310',OriginationIdentity:process.env.AWS_ORIGINATION_IDENTITY,MessageBody:'Example message',MessageType:'TRANSACTIONAL'})); console.log(result.MessageId);

Python

import boto3, os
result=boto3.client('pinpoint-sms-voice-v2',region_name=os.environ['AWS_REGION']).send_text_message(DestinationPhoneNumber='+15558675310',OriginationIdentity=os.environ['AWS_ORIGINATION_IDENTITY'],MessageBody='Example message',MessageType='TRANSACTIONAL')
print(result['MessageId'])

Go

package main

import (
	"context"
	"fmt"
	"github.com/aws/aws-sdk-go-v2/config"
	sms "github.com/aws/aws-sdk-go-v2/service/pinpointsmsvoicev2"
	"os"
)

func main() {
	cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(os.Getenv("AWS_REGION")))
	if err != nil {
		panic(err)
	}
	out, err := sms.NewFromConfig(cfg).SendTextMessage(context.Background(), &sms.SendTextMessageInput{DestinationPhoneNumber: &[]string{"+15558675310"}[0], OriginationIdentity: &[]string{os.Getenv("AWS_ORIGINATION_IDENTITY")}[0], MessageBody: &[]string{"Example message"}[0], MessageType: "TRANSACTIONAL"})
	if err != nil {
		panic(err)
	}
	fmt.Println(*out.MessageId)
}

See AWS's SendTextMessage API.

Storage

Amazon S3 — s3:use and POST /s3/presign

Gateman signs a native GetObject or PutObject request and returns a URL. Direct access moves that signing operation into the application. Object bytes still transfer directly between the application and S3.

PHP

require 'vendor/autoload.php'; use Aws\S3\S3Client;
$s3=new S3Client(['version'=>'latest','region'=>getenv('AWS_REGION')]); $command=$s3->getCommand('GetObject',['Bucket'=>getenv('S3_BUCKET'),'Key'=>'reports/example.pdf']); echo (string)$s3->createPresignedRequest($command,'+15 minutes')->getUri();

Node.js

import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const url=await getSignedUrl(new S3Client({region:process.env.AWS_REGION}),new GetObjectCommand({Bucket:process.env.S3_BUCKET,Key:'reports/example.pdf'}),{expiresIn:900}); console.log(url);

Python

import boto3, os
print(boto3.client('s3',region_name=os.environ['AWS_REGION']).generate_presigned_url('get_object',Params={'Bucket':os.environ['S3_BUCKET'],'Key':'reports/example.pdf'},ExpiresIn=900))

Go

package main

import (
	"context"
	"fmt"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"os"
)

func main() {
	cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(os.Getenv("AWS_REGION")))
	if err != nil {
		panic(err)
	}
	p := s3.NewPresignClient(s3.NewFromConfig(cfg))
	out, err := p.PresignGetObject(context.Background(), &s3.GetObjectInput{Bucket: aws.String(os.Getenv("S3_BUCKET")), Key: aws.String("reports/example.pdf")})
	if err != nil {
		panic(err)
	}
	fmt.Println(out.URL)
}

See AWS S3 presigned URLs.

Cloudflare R2 — r2:use and POST /r2/presign

R2 uses an S3-compatible presigner. Use the account-specific S3 endpoint, R2 S3 credentials, and region: auto. A jurisdiction-specific endpoint is required when the bucket belongs to a defined jurisdiction.

PHP

require 'vendor/autoload.php'; use Aws\S3\S3Client;
$s3=new S3Client(['version'=>'latest','region'=>'auto','endpoint'=>'https://'.getenv('R2_ACCOUNT_ID').'.r2.cloudflarestorage.com','credentials'=>['key'=>getenv('R2_ACCESS_KEY_ID'),'secret'=>getenv('R2_SECRET_ACCESS_KEY')]]); $command=$s3->getCommand('GetObject',['Bucket'=>getenv('R2_BUCKET'),'Key'=>'reports/example.pdf']); echo (string)$s3->createPresignedRequest($command,'+15 minutes')->getUri();

Node.js

import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const client=new S3Client({region:'auto',endpoint:`https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,credentials:{accessKeyId:process.env.R2_ACCESS_KEY_ID,secretAccessKey:process.env.R2_SECRET_ACCESS_KEY}}); console.log(await getSignedUrl(client,new GetObjectCommand({Bucket:process.env.R2_BUCKET,Key:'reports/example.pdf'}),{expiresIn:900}));

Python

import boto3, os
client=boto3.client('s3',region_name='auto',endpoint_url=f"https://{os.environ['R2_ACCOUNT_ID']}.r2.cloudflarestorage.com",aws_access_key_id=os.environ['R2_ACCESS_KEY_ID'],aws_secret_access_key=os.environ['R2_SECRET_ACCESS_KEY']); print(client.generate_presigned_url('get_object',Params={'Bucket':os.environ['R2_BUCKET'],'Key':'reports/example.pdf'},ExpiresIn=900))

Go

package main

import (
	"context"
	"fmt"
	aws "github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"os"
)

func main() {
	cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion("auto"), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(os.Getenv("R2_ACCESS_KEY_ID"), os.Getenv("R2_SECRET_ACCESS_KEY"), "")))
	if err != nil {
		panic(err)
	}
	client := s3.NewFromConfig(cfg, func(o *s3.Options) {
		o.BaseEndpoint = aws.String("https://" + os.Getenv("R2_ACCOUNT_ID") + ".r2.cloudflarestorage.com")
		o.UsePathStyle = true
	})
	p := s3.NewPresignClient(client)
	out, err := p.PresignGetObject(context.Background(), &s3.GetObjectInput{Bucket: aws.String(os.Getenv("R2_BUCKET")), Key: aws.String("reports/example.pdf")})
	if err != nil {
		panic(err)
	}
	fmt.Println(out.URL)
}

See Cloudflare's R2 S3 API, presigned URLs, and R2 credentials.

AWS and Cloudflare administration

AWS — aws:use and POST /aws/request

Gateman accepts a native AWS HTTP request and adds SigV4 signing. Direct code can use an AWS SDK, which is generally safer than reproducing SigV4 manually. This read-only example lists IAM roles.

PHP

require 'vendor/autoload.php'; $iam=new Aws\Iam\IamClient(['version'=>'latest','region'=>getenv('AWS_REGION')]); foreach($iam->getPaginator('ListRoles') as $page) foreach($page['Roles'] as $role) echo $role['RoleName']."\n";

Node.js

import { IAMClient, ListRolesCommand } from '@aws-sdk/client-iam'; const result=await new IAMClient({region:process.env.AWS_REGION}).send(new ListRolesCommand({})); console.log(result.Roles?.map(role=>role.RoleName));

Python

import boto3, os
for page in boto3.client('iam',region_name=os.environ['AWS_REGION']).get_paginator('list_roles').paginate(): print([role['RoleName'] for role in page['Roles']])

Go

package main

import (
	"context"
	"fmt"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/iam"
	"os"
)

func main() {
	cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(os.Getenv("AWS_REGION")))
	if err != nil {
		panic(err)
	}
	out, err := iam.NewFromConfig(cfg).ListRoles(context.Background(), &iam.ListRolesInput{})
	if err != nil {
		panic(err)
	}
	for _, role := range out.Roles {
		fmt.Println(*role.RoleName)
	}
}

The SDK call is the direct equivalent of constructing the AWS native request; the Gateman outer JSON envelope is not an AWS SDK argument.

Cloudflare — cloudflare:use and POST /cloudflare/request

Direct access uses a Cloudflare API token and the API v4 origin. The provider path, query, JSON body, and response remain recognizable. This read-only example lists accounts.

PHP

$ch=curl_init('https://api.cloudflare.com/client/v4/accounts'); curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_HTTPHEADER=>['Authorization: Bearer '.getenv('CLOUDFLARE_API_TOKEN'),'Accept: application/json']]); $response=curl_exec($ch); if ($response===false || curl_getinfo($ch,CURLINFO_RESPONSE_CODE)>=400) throw new RuntimeException('Cloudflare request failed'); echo $response;

Node.js

const response=await fetch('https://api.cloudflare.com/client/v4/accounts',{headers:{Authorization:`Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,Accept:'application/json'}}); if (!response.ok) throw new Error(`Cloudflare HTTP ${response.status}`); console.log(await response.json());

Python

import os, urllib.request, json
req=urllib.request.Request('https://api.cloudflare.com/client/v4/accounts',headers={'Authorization':'Bearer '+os.environ['CLOUDFLARE_API_TOKEN'],'Accept':'application/json'});
with urllib.request.urlopen(req) as response:
    if response.status >= 400: raise RuntimeError(f'Cloudflare HTTP {response.status}')
    print(json.load(response))

Go

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.cloudflare.com/client/v4/accounts", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("CLOUDFLARE_API_TOKEN"))
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("Cloudflare HTTP %s", res.Status))
	}
	b, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

See Cloudflare's API documentation.

Anti-bot and credential retrieval

Turnstile and reCAPTCHA

anti_bot.verify returns configured site and secret keys. It does not receive visitor tokens or perform verification. Removing Gateman means configuring the keys directly through the application's chosen secure mechanism and calling the provider's verification endpoint from the trusted backend.

The examples below verify tokens directly. TURNSTILE_TOKEN and RECAPTCHA_TOKEN are application-provided values obtained from the submitted form; they are not Gateman values.

PHP

$tokenFromYourForm=getenv('TURNSTILE_TOKEN'); if (!$tokenFromYourForm) throw new RuntimeException('TURNSTILE_TOKEN is required'); $ch=curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify'); curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_POSTFIELDS=>http_build_query(['secret'=>getenv('TURNSTILE_SECRET_KEY'),'response'=>$tokenFromYourForm])]); $resultBody=curl_exec($ch); $status=curl_getinfo($ch,CURLINFO_RESPONSE_CODE); if ($resultBody===false || $status>=400) throw new RuntimeException('Turnstile HTTP request failed'); $result=json_decode($resultBody,true,512,JSON_THROW_ON_ERROR); var_dump($result['success'] ?? false);

Node.js

const tokenFromYourForm=process.env.TURNSTILE_TOKEN; if (!tokenFromYourForm) throw new Error('TURNSTILE_TOKEN is required'); const response=await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({secret:process.env.TURNSTILE_SECRET_KEY,response:tokenFromYourForm})}); if (!response.ok) throw new Error(`Turnstile HTTP ${response.status}`); console.log(await response.json());

Python

import os, urllib.error, urllib.parse, urllib.request, json
token_from_your_form=os.environ['TURNSTILE_TOKEN']; req=urllib.request.Request('https://challenges.cloudflare.com/turnstile/v0/siteverify',data=urllib.parse.urlencode({'secret':os.environ['TURNSTILE_SECRET_KEY'],'response':token_from_your_form}).encode(),method='POST');
try:
    with urllib.request.urlopen(req) as response:
        if response.status >= 400: raise RuntimeError(f'Turnstile HTTP {response.status}')
        print(json.load(response))
except urllib.error.HTTPError as error:
    raise RuntimeError(f'Turnstile HTTP {error.code}') from error

Go

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
)

func main() {
	token := os.Getenv("TURNSTILE_TOKEN")
	if token == "" {
		panic("TURNSTILE_TOKEN is required")
	}
	form := url.Values{"secret": {os.Getenv("TURNSTILE_SECRET_KEY")}, "response": {token}}
	req, err := http.NewRequest("POST", "https://challenges.cloudflare.com/turnstile/v0/siteverify", strings.NewReader(form.Encode()))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("Turnstile HTTP %s", res.Status))
	}
	b, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

Google reCAPTCHA — siteverify

Google reCAPTCHA verification uses the application-provided token and the secret key at Google's siteverify endpoint. Check success and, for score- based keys, the expected action and hostname in application code.

PHP

$token=getenv('RECAPTCHA_TOKEN'); if (!$token) throw new RuntimeException('RECAPTCHA_TOKEN is required'); $ch=curl_init('https://www.google.com/recaptcha/api/siteverify'); curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_POSTFIELDS=>http_build_query(['secret'=>getenv('RECAPTCHA_SECRET_KEY'),'response'=>$token])]); $body=curl_exec($ch); $status=curl_getinfo($ch,CURLINFO_RESPONSE_CODE); if ($body===false || $status>=400) throw new RuntimeException('reCAPTCHA HTTP request failed'); $result=json_decode($body,true,512,JSON_THROW_ON_ERROR); var_dump($result['success'] ?? false);

Node.js

const token=process.env.RECAPTCHA_TOKEN; if (!token) throw new Error('RECAPTCHA_TOKEN is required'); const response=await fetch('https://www.google.com/recaptcha/api/siteverify',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({secret:process.env.RECAPTCHA_SECRET_KEY,response:token})}); if (!response.ok) throw new Error(`reCAPTCHA HTTP ${response.status}`); console.log(await response.json());

Python

import json, os, urllib.error, urllib.parse, urllib.request
token=os.environ['RECAPTCHA_TOKEN']; req=urllib.request.Request('https://www.google.com/recaptcha/api/siteverify',data=urllib.parse.urlencode({'secret':os.environ['RECAPTCHA_SECRET_KEY'],'response':token}).encode(),method='POST')
try:
    with urllib.request.urlopen(req) as response:
        if response.status >= 400: raise RuntimeError(f'reCAPTCHA HTTP {response.status}')
        print(json.load(response))
except urllib.error.HTTPError as error:
    raise RuntimeError(f'reCAPTCHA HTTP {error.code}') from error

Go

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strings"
)

func main() {
	token := os.Getenv("RECAPTCHA_TOKEN")
	if token == "" {
		panic("RECAPTCHA_TOKEN is required")
	}
	form := url.Values{"secret": {os.Getenv("RECAPTCHA_SECRET_KEY")}, "response": {token}}
	req, err := http.NewRequest("POST", "https://www.google.com/recaptcha/api/siteverify", strings.NewReader(form.Encode()))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("reCAPTCHA HTTP %s", res.Status))
	}
	body, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	var result map[string]any
	if err := json.Unmarshal(body, &result); err != nil {
		panic(err)
	}
	fmt.Println(result["success"])
}

The browser widget, token lifecycle, hostname/action/score policy, and final allow/deny decision remain application responsibilities. See Turnstile validation and reCAPTCHA verification.

Stored credentials and database passwords

secrets:read and db:read are Gateman storage capabilities, not provider APIs. If they are removed, the application must obtain the required values from a customer-selected source. No particular secrets manager, environment layout, database architecture, or deployment model is required by this guide.

Stripe webhook secrets and setup

POST /stripe/webhook-secret retrieves a Gateman-stored signing secret; it is not a Stripe API request. POST /stripe/webhook/setup configures a supported Stripe endpoint using the application's credentials. Direct integrations can use Stripe's webhook endpoint API or dashboard, then store the returned secret and verify events in the application's own receiver. Do not change or delete an existing endpoint merely because Gateman is no longer used.

See Stripe's webhook endpoint API and signature verification.

The read-only example below lists existing webhook endpoints. It demonstrates the direct Stripe authentication and response handling without creating, updating, or deleting a webhook endpoint.

PHP

$ch=curl_init('https://api.stripe.com/v1/webhook_endpoints?limit=10'); curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_USERPWD=>getenv('STRIPE_SECRET_KEY').':',CURLOPT_HTTPHEADER=>['Accept: application/json']]); $body=curl_exec($ch); $status=curl_getinfo($ch,CURLINFO_RESPONSE_CODE); if ($body===false || $status>=400) throw new RuntimeException('Stripe webhook HTTP request failed'); echo $body;

Node.js

const response=await fetch('https://api.stripe.com/v1/webhook_endpoints?limit=10',{headers:{Authorization:`Bearer ${process.env.STRIPE_SECRET_KEY}`,Accept:'application/json'}}); if (!response.ok) throw new Error(`Stripe webhook HTTP ${response.status}`); console.log(await response.json());

Python

import base64, json, os, urllib.error, urllib.request
request=urllib.request.Request('https://api.stripe.com/v1/webhook_endpoints?limit=10',headers={'Authorization':'Basic '+base64.b64encode((os.environ['STRIPE_SECRET_KEY']+':').encode()).decode(),'Accept':'application/json'})
try:
    with urllib.request.urlopen(request) as response:
        if response.status >= 400: raise RuntimeError(f'Stripe webhook HTTP {response.status}')
        print(json.load(response))
except urllib.error.HTTPError as error:
    raise RuntimeError(f'Stripe webhook HTTP {error.code}') from error

Go

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.stripe.com/v1/webhook_endpoints?limit=10", nil)
	if err != nil {
		panic(err)
	}
	req.SetBasicAuth(os.Getenv("STRIPE_SECRET_KEY"), "")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		panic(fmt.Sprintf("Stripe webhook HTTP %s", res.Status))
	}
	body, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(body))
}

WordPress

A WordPress customer can use another mail transport or payment integration instead of the GetGateman integration. The appropriate change depends on the site's existing configuration; removing the plugin, store data, orders, or provider resources is not a universal requirement. See GetGateman for WordPress.